diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index ac927e9a19426..329ba3a348ed7 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -9,10 +9,9 @@ use rustc_trait_selection::traits::auto_trait::{self, RegionTarget}; use thin_vec::ThinVec; -use crate::clean::{self, simplify, Lifetime}; +use crate::clean::{self, Lifetime}; use crate::clean::{ - clean_generic_param_def, clean_middle_ty, clean_predicate, clean_trait_ref_with_constraints, - clean_ty_generics, + clean_generic_param_def, clean_middle_ty, clean_trait_ref_with_constraints, clean_ty_generics, }; use crate::core::DocContext; @@ -169,7 +168,7 @@ fn clean_param_env<'tcx>( // FIXME(#111101): Incorporate the explicit predicates of the item here... let item_predicates: FxIndexSet<_> = tcx.param_env(item_def_id).caller_bounds().iter().collect(); - let where_predicates = param_env + let predicates = param_env .caller_bounds() .iter() // FIXME: ...which hopefully allows us to simplify this: @@ -194,14 +193,14 @@ fn clean_param_env<'tcx>( } }) }) - .flat_map(|pred| clean_predicate(pred, cx)) - .chain(clean_region_outlives_constraints(®ion_data, generics)) + .map(|pred| (pred, rustc_span::DUMMY_SP)) .collect(); - let mut generics = clean::Generics { params, where_predicates }; - simplify::sized_bounds(cx, &mut generics); - generics.where_predicates = simplify::where_clauses(cx, generics.where_predicates); - generics + let mut where_predicates = super::modern::clean_predicates(cx, predicates); + // FIXME: these no longer get "simplif[ied]::where_clauses" + where_predicates.extend(clean_region_outlives_constraints(®ion_data, generics)); + + clean::Generics { params, where_predicates } } /// Clean region outlives constraints to where-predicates. diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 22565ea402803..e73d6912c680f 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -25,16 +25,17 @@ mod auto_trait; mod blanket_impl; pub(crate) mod cfg; pub(crate) mod inline; +// FIXME: temporary module +mod modern; mod render_macro_matchers; mod simplify; pub(crate) mod types; pub(crate) mod utils; - use rustc_ast as ast; use rustc_ast::token::{Token, TokenKind}; use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_attr as attr; -use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet, IndexEntry}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet, IndexEntry}; use rustc_errors::{codes::*, struct_span_code_err, FatalError}; use rustc_hir as hir; use rustc_hir::def::{CtorKind, DefKind, Res}; @@ -44,8 +45,8 @@ use rustc_hir_analysis::lower_ty; use rustc_middle::metadata::Reexport; use rustc_middle::middle::resolve_bound_vars as rbv; use rustc_middle::ty::GenericArgsRef; -use rustc_middle::ty::TypeVisitableExt; use rustc_middle::ty::{self, AdtKind, Ty, TyCtxt}; +use rustc_middle::ty::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt}; use rustc_middle::{bug, span_bug}; use rustc_span::hygiene::{AstPass, MacroKind}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; @@ -55,6 +56,7 @@ use rustc_trait_selection::traits::wf::object_region_bounds; use std::borrow::Cow; use std::collections::BTreeMap; use std::mem; +use std::ops::ControlFlow; use thin_vec::ThinVec; use crate::core::DocContext; @@ -349,73 +351,6 @@ fn clean_where_predicate<'tcx>( }) } -pub(crate) fn clean_predicate<'tcx>( - predicate: ty::Clause<'tcx>, - cx: &mut DocContext<'tcx>, -) -> Option { - 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) => clean_region_outlives_predicate(pred), - ty::ClauseKind::TypeOutlives(pred) => { - 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(..) => None, - } -} - -fn clean_poly_trait_predicate<'tcx>( - pred: ty::PolyTraitPredicate<'tcx>, - cx: &mut DocContext<'tcx>, -) -> Option { - // `T: ~const Destruct` is hidden because `T: Destruct` is a no-op. - // FIXME(effects) 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>, -) -> Option { - let ty::OutlivesPredicate(a, b) = pred; - - Some(WherePredicate::RegionPredicate { - lifetime: clean_middle_region(a).expect("failed to clean lifetime"), - bounds: vec![GenericBound::Outlives( - clean_middle_region(b).expect("failed to clean bounds"), - )], - }) -} - -fn clean_type_outlives_predicate<'tcx>( - pred: ty::Binder<'tcx, ty::TypeOutlivesPredicate<'tcx>>, - cx: &mut DocContext<'tcx>, -) -> Option { - let ty::OutlivesPredicate(ty, lt) = pred.skip_binder(); - - Some(WherePredicate::BoundPredicate { - ty: clean_middle_ty(pred.rebind(ty), cx, None, None), - bounds: vec![GenericBound::Outlives( - clean_middle_region(lt).expect("failed to clean lifetimes"), - )], - bound_params: Vec::new(), - }) -} - fn clean_middle_term<'tcx>( term: ty::Binder<'tcx, ty::Term<'tcx>>, cx: &mut DocContext<'tcx>, @@ -436,24 +371,6 @@ fn clean_hir_term<'tcx>(term: &hir::Term<'tcx>, cx: &mut DocContext<'tcx>) -> Te } } -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| { - // FIXME: This needs to be made resilient for `AliasTerm`s that - // are associated consts. - p.projection_term.expect_ty(cx.tcx) - }), - cx, - None, - ), - rhs: clean_middle_term(pred.map_bound(|p| p.term), cx), - } -} - fn clean_projection<'tcx>( ty: ty::Binder<'tcx, ty::AliasTy<'tcx>>, cx: &mut DocContext<'tcx>, @@ -464,8 +381,7 @@ fn clean_projection<'tcx>( .tcx .explicit_item_bounds(ty.skip_binder().def_id) .iter_instantiated_copied(cx.tcx, ty.skip_binder().args) - .map(|(pred, _)| pred) - .collect::>(); + .collect(); return clean_middle_opaque_bounds(cx, bounds); } @@ -496,6 +412,7 @@ fn compute_should_show_cast(self_def_id: Option, trait_: &Path, self_type .map_or(!self_type.is_self_type(), |(id, trait_)| id != trait_) } +// FIXME: Generalize to `clean_middle_projection_term` fn projection_to_path_segment<'tcx>( ty: ty::Binder<'tcx, ty::AliasTy<'tcx>>, cx: &mut DocContext<'tcx>, @@ -784,19 +701,19 @@ pub(crate) fn clean_generics<'tcx>( } } +// FIXME(fmease): Explain why we need to take `predicates`, too! fn clean_ty_generics<'tcx>( cx: &mut DocContext<'tcx>, - gens: &ty::Generics, - preds: ty::GenericPredicates<'tcx>, + generics: &'tcx ty::Generics, + predicates: 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::>::default(); + let mut apits = BTreeMap::new(); - let params: ThinVec<_> = gens + let params = generics .own_params .iter() .filter(|param| match param.kind { + // FIXME(fmease): Explain why we can get anonymous lifetimes. ty::GenericParamDefKind::Lifetime => !param.is_anonymous_lifetime(), ty::GenericParamDefKind::Type { synthetic, .. } => { if param.name == kw::SelfUpper { @@ -804,7 +721,7 @@ fn clean_ty_generics<'tcx>( return false; } if synthetic { - impl_trait.insert(param.index, vec![]); + apits.insert(param.index, Vec::new()); return false; } true @@ -814,115 +731,70 @@ fn clean_ty_generics<'tcx>( .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::>)>>::default(); - - let where_predicates = preds - .predicates - .iter() - .flat_map(|(pred, _)| { - let mut projection = 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() { - return Some(param.index); - } - } - ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty, _reg)) => { - if let ty::Param(param) = ty.kind() { - return Some(param.index); - } - } - ty::ClauseKind::Projection(p) => { - if let ty::Param(param) = p.projection_term.self_ty().kind() { - projection = Some(bound_p.rebind(p)); - return 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)?; + struct ApitFinder<'a, 'tcx> { + apits: &'a BTreeMap, rustc_span::Span)>>, + } - bounds.extend(pred.get_bounds().into_iter().flatten().cloned()); + impl<'tcx> ty::TypeVisitor> for ApitFinder<'_, 'tcx> { + type Result = ControlFlow; - if let Some(proj) = projection - && let lhs = clean_projection( - proj.map_bound(|p| { - // FIXME: This needs to be made resilient for `AliasTerm`s that - // are associated consts. - p.projection_term.expect_ty(cx.tcx) - }), - cx, - None, - ) - && let Some((_, trait_did, name)) = lhs.projection() - { - impl_trait_proj.entry(param_idx).or_default().push(( - trait_did, - name, - proj.map_bound(|p| p.term), - )); + fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { + match ty.kind() { + ty::Param(param_ty) => { + if self.apits.contains_key(¶m_ty.index) { + // FIXME: rephrase (there's no pred in scope) + // A predicate may at most contain a single APIT. + return ControlFlow::Break(param_ty.index); + } } - - return None; + _ if ty.has_param() => return ty.super_visit_with(self), + _ => {} } - - Some(pred) - }) - .collect::>(); - - 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 { - 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().map_or(true, |b| !b.is_trait_bound()) { - bounds.insert(0, GenericBound::sized(cx)); + ControlFlow::Continue(()) } + } - 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); + // FIXME: explainer + let predicates = predicates + .predicates + .iter() + .copied() + .filter(|&(predicate, span)| { + if let ControlFlow::Break(index) = + predicate.visit_with(&mut ApitFinder { apits: &apits }) + { + apits.get_mut(&index).unwrap().push((predicate, span)); + return false; } - } + true + }) + .collect(); - cx.impl_trait_bounds.insert(idx.into(), bounds); + // FIXME: does this handle Sized/?Sized properly? + for (index, predicates) in apits { + // FIXME: fix up API of clean_pred instead + let mut where_predicates = modern::clean_predicates(cx, predicates); + let Some(WherePredicate::BoundPredicate { bounds, .. }) = where_predicates.pop() else { + unreachable!() + }; + cx.impl_trait_bounds.insert(index.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 where_predicates = modern::clean_predicates(cx, predicates); + + // FIXME: we no longer have access to `sized: UnordMap` + // for param in &generics.own_params { + // if !sized.contains(¶m.index) { + // // FIXME: is this correct if we have parent generics? + // where_predicates.push(WherePredicate::BoundPredicate { + // ty: Type::Generic(param.name), + // bounds: vec![GenericBound::maybe_sized(cx)], + // bound_params: Vec::new(), + // }) + // } + // } - let mut generics = Generics { params, where_predicates }; - simplify::sized_bounds(cx, &mut generics); - generics.where_predicates = simplify::where_clauses(cx, generics.where_predicates); - generics + Generics { params, where_predicates } } fn clean_ty_alias_inner_type<'tcx>( @@ -1379,6 +1251,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( TyMethodItem(item) } } + // FIXME: Rewrite this!!! ty::AssocKind::Type => { let my_name = assoc_item.name; @@ -2124,7 +1997,8 @@ pub(crate) fn clean_middle_ty<'tcx>( }) .collect::>(); - let bindings = obj + // FIXME: yooo + let constraints = obj .projection_bounds() .map(|pb| AssocItemConstraint { assoc: projection_to_path_segment( @@ -2160,7 +2034,7 @@ pub(crate) fn clean_middle_ty<'tcx>( .collect(); let late_bound_regions = late_bound_regions.into_iter().collect(); - let path = clean_middle_path(cx, did, false, bindings, args); + let path = clean_middle_path(cx, did, false, constraints, args); bounds.insert(0, PolyTrait { trait_: path, generic_params: late_bound_regions }); DynTrait(bounds, lifetime) @@ -2243,8 +2117,7 @@ pub(crate) fn clean_middle_ty<'tcx>( .tcx .explicit_item_bounds(def_id) .iter_instantiated_copied(cx.tcx, args) - .map(|(bound, _)| bound) - .collect::>(); + .collect(); let ty = clean_middle_opaque_bounds(cx, bounds); if let Some(count) = cx.current_type_aliases.get_mut(&def_id) { *count -= 1; @@ -2268,60 +2141,18 @@ pub(crate) fn clean_middle_ty<'tcx>( fn clean_middle_opaque_bounds<'tcx>( cx: &mut DocContext<'tcx>, - bounds: Vec>, + predicates: Vec<(ty::Clause<'tcx>, rustc_span::Span)>, ) -> Type { - let mut has_sized = false; - let mut bounds = bounds - .iter() - .filter_map(|bound| { - let bound_predicate = bound.kind(); - let trait_ref = match bound_predicate.skip_binder() { - ty::ClauseKind::Trait(tr) => bound_predicate.rebind(tr.trait_ref), - ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(_ty, reg)) => { - return clean_middle_region(reg).map(GenericBound::Outlives); - } - _ => 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| { - if let ty::ClauseKind::Projection(proj) = bound.kind().skip_binder() { - if proj.projection_term.trait_ref(cx.tcx) == trait_ref.skip_binder() { - Some(AssocItemConstraint { - assoc: projection_to_path_segment( - // FIXME: This needs to be made resilient for `AliasTerm`s that - // are associated consts. - bound.kind().rebind(proj.projection_term.expect_ty(cx.tcx)), - cx, - ), - kind: AssocItemConstraintKind::Equality { - term: clean_middle_term(bound.kind().rebind(proj.term), cx), - }, - }) - } else { - None - } - } else { - None - } - }) - .collect(); - - Some(clean_poly_trait_ref_with_constraints(cx, trait_ref, bindings)) - }) - .collect::>(); + // FIXME: fix up API of clean_pred instead + // FIXME: we currentyl elide `Sized` bc it looks for bounded_ty=`ty::Param` but we don't + // care about that here bc we want to look for bounded_ty=Alias(Opaque) (which we can + // actually assume / don't need to check) + let mut where_predicates = modern::clean_predicates(cx, predicates); + let Some(WherePredicate::BoundPredicate { mut bounds, .. }) = where_predicates.pop() else { + unreachable!() + }; - if !has_sized { - bounds.push(GenericBound::maybe_sized(cx)); - } + // FIXME: rewrite this, too // Move trait bounds to the front. bounds.sort_by_key(|b| !b.is_trait_bound()); diff --git a/src/librustdoc/clean/modern.rs b/src/librustdoc/clean/modern.rs new file mode 100644 index 0000000000000..f882afeee3d82 --- /dev/null +++ b/src/librustdoc/clean/modern.rs @@ -0,0 +1,319 @@ +#![allow(dead_code)] // FIXME + +use rustc_data_structures::unord::UnordSet; +use rustc_middle::ty::ToPolyTraitRef; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_span::{symbol::kw, Span}; + +use thin_vec::ThinVec; + +use crate::core::DocContext; + +use super::{clean_middle_region, clean_middle_term, clean_middle_ty}; +use super::{clean_poly_trait_ref_with_constraints, projection_to_path_segment}; +use super::{AssocItemConstraint, AssocItemConstraintKind, GenericBound, WherePredicate}; + +// FIXME(return_type_notation, #123996): Support RTN. +// FIXME(async_closure): De-lower `AsyncFn*` bounds to `async Fn*` ones. + +pub(super) fn clean_predicates<'tcx>( + cx: &mut DocContext<'tcx>, + mut predicates: Vec<(ty::Clause<'tcx>, Span)>, +) -> ThinVec { + // FIXME: Ideally we'd only sort here if the item is an impl block (NB: def_kind() isn't reliable here + // bc the "item_def_id" doesn't refer to an impl block if this impl was synth'ed by auto_trait). + // FIXME: Also explain why we're doing this (at least for impls): namely `gather_explicit_predicates_of` + // reorders preds via `cgp::setup_constraining_predicates` if it's an impl + predicates.sort_by_key(|&(_, span)| span); + + let mut clauses = Default::default(); + let mut sized = Default::default(); + + for (predicate, span) in predicates { + group_predicate(cx.tcx, predicate, span, &mut clauses, &mut sized); + } + + // FIXME: Explain that reason why we need to go over stuff twice is because + // we first need to gather all projection predicates to be able to call + // clean_poly_trait_with_constraints which requires a full set of constraints + // NOTE: However, we might just get away with calling clean_poly_trait_ref + // and pushing the constraints later (similar to `simplify::merge_bounds`). + // FIXME: however, it makes things nicer actually to operate on ty:: things, not clean:: ones + // (interned equality, etc) + + clauses + .into_iter() + .filter_map(|clause| match clause { + Clause::BoundedTy { ty, bounds, .. } => clean_ty_clause(cx, ty, bounds, &mut sized), + Clause::BoundedRe { re, bounds } => Some(clean_re_clause(re, bounds)), + }) + .collect() +} + +// FIXME: horrendous name +fn group_predicate<'tcx>( + tcx: TyCtxt<'tcx>, + predicate: ty::Clause<'tcx>, + span: Span, + clauses: &mut Vec>, // FIXME: ugly out param + sized: &mut UnordSet, // FIXME: ugly out param +) { + let kind = predicate.kind(); + match kind.skip_binder() { + // FIXME(fmease): Acknowledge that we intentionally ignore polarity! + ty::ClauseKind::Trait(pred) => { + let pred = kind.rebind(pred); + let bounded_ty = pred.self_ty(); + + match *bounded_ty.skip_binder().kind() { + // FIXME: does this correctly deal with `Self`? I don't think so + // FIXME: not only look for `ty::Param` but also for `ty::Alias(ty::Opaque)` (kinda) + // however, we don't want to handle that here, this should depend on the + // user (parametrization) + ty::Param(param_ty) => { + if tcx.is_lang_item(pred.def_id(), rustc_hir::LangItem::Sized) + && param_ty.name != kw::SelfUpper + { + sized.insert(param_ty.index); + // FIXME: Instead of unconditionally dropping `Sized` bounds, we could make it so + // we only drop synthetic ones (by checking if the Span coincides with the span of + // the type parameter if the bounded ty is a type parameter). + return; + } + } + // FIXME: I don't think this can handle `Trait>` (namely `Inner == ()`) + ty::Alias(ty::Projection, bounded_alias_ty) => { + if let Some(Clause::BoundedTy { bounds, span: clause_span, .. }) = clauses.last_mut() + && clause_span.contains(span) // FIXME: explainer // FIXME: this is not suffient we need to check instance_of + && let Some(TyBound::Trait(trait_ref, constraints)) = bounds.last_mut() + // FIXME: explain why skip_binder is okay here (relates to comment above) + && is_instance_of(tcx, trait_ref.skip_binder(), bounded_alias_ty.trait_ref(tcx)) + { + // FIXME: ^ support constraints on this bound ^^' + let bound = pred.to_poly_trait_ref(); + let bounded_alias_ty = pred.rebind(bounded_alias_ty); + + if let Some(Constraint::Bounds(alias_ty, alias_bounds)) = constraints.last_mut() + // FIXME: Audit (do we need to use is_instance of) + && *alias_ty == bounded_alias_ty + { + alias_bounds.push(bound); + } else { + constraints.push(Constraint::Bounds(bounded_alias_ty, vec![bound])); + } + return; + } + } + _ => {} + } + + // FIXME: Skip `Destruct` here + // // `T: ~const Destruct` is hidden because `T: Destruct` is a no-op. + // // FIXME(effects) check constness + // if tcx.is_lang_item(pred.def_id(), hir::LangItem::Destruct) { + // return None; + // } + + let bound = TyBound::Trait(pred.to_poly_trait_ref(), Vec::new()); + + if let Some(Clause::BoundedTy { ty, bounds, .. }) = clauses.last_mut() + && *ty == bounded_ty // FIXME: explain why `==` is okay here + { + bounds.push(bound); + } else { + clauses.push(Clause::BoundedTy { ty: bounded_ty, bounds: vec![bound], span }); + } + } + ty::ClauseKind::Projection(pred) => { + // FIXME: explain why `no_bound_vars` is not correct here (I think) + // FIXME: Add note: Projections don't support binders (yet). + // Would be (surface) `T: Trait T<'a> = &'a ()>` + // (contrary to `for<'a> T: ...` or `T: for<'a> ...`) + // assert!(kind.bound_vars().is_empty()); + + // FIXME: explain why it's fine to ignre polarity + if let Some(Clause::BoundedTy { bounds, .. }) = clauses.last_mut() + && let Some(TyBound::Trait(trait_ref, constraints)) = bounds.last_mut() + // FIXME: Explain why this seems to be necessary + && is_instance_of(tcx, trait_ref.skip_binder(), pred.projection_term.trait_ref(tcx)) + { + // FIXME: explain why skip_binder is okay here (relates to comment above) + // FIXME: explain why this holds + // FIXME: this no longer holds + // debug_assert!(is_instance_of(tcx, trait_ref.skip_binder(), pred.projection_term.trait_ref(tcx))); + + constraints.push(Constraint::Equality(kind.rebind(pred))); + } else { + // FIXME: Explainer (synthetic `-> ()` has same span as trait ref, therefore they don't get placed after it). + // FIXME: make this more robust by also checking that the trait ref is a fn-family trait ref (incl. async ones) + // debug_assert_eq!(pred.term.as_type(), Some(tcx.types.unit)); + + // FIXME: Explainer why it's fine to drop things + // FIXME: however, make sure that `auto_trait.rs` actually conforms to the scheme + } + } + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(bounded_re, bound)) => { + if let Some(Clause::BoundedRe { re, bounds }) = clauses.last_mut() + && *re == bounded_re + { + bounds.push(bound); + } else { + clauses.push(Clause::BoundedRe { re: bounded_re, bounds: vec![bound] }); + } + } + // FIXME: We also need to look for Alias(Projection): 'a to be able to resugar + // associated type bounds of the form `Trait` + ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(bounded_ty, bound)) => { + let bounded_ty = kind.rebind(bounded_ty); + if let Some(Clause::BoundedTy { ty, bounds, .. }) = clauses.last_mut() + && *ty == bounded_ty // FIXME: explain why `==` is okay here + { + bounds.push(TyBound::Outlives(bound)); + } else { + clauses.push(Clause::BoundedTy { ty: bounded_ty, bounds: vec![TyBound::Outlives(bound)], span }); + } + } + ty::ClauseKind::ConstArgHasType(_, _) + | ty::ClauseKind::WellFormed(_) + // FIXME(fmease): Check if we need to reify this for GCEs + | ty::ClauseKind::ConstEvaluatable(_) => {} + } +} + +fn clean_ty_clause<'tcx>( + cx: &mut DocContext<'tcx>, + bounded_ty: ty::Binder<'tcx, Ty<'tcx>>, + bounds: Vec>, + sized: &mut UnordSet, +) -> Option { + let mut bounds: Vec<_> = bounds.into_iter().map(|bound| clean_ty_bound(cx, bound)).collect(); + + if let ty::Param(param_ty) = bounded_ty.skip_binder().kind() + && param_ty.name != kw::SelfUpper + // FIXME: HACK + && sized.insert(param_ty.index) + { + bounds.push(GenericBound::maybe_sized(cx)); + } + + if bounds.is_empty() { + // FIXME: We might want to keep `where T:` (user-written) (Wf). + // However, we do need to skip here due to Sized/?Sized logic + return None; + } + + Some(WherePredicate::BoundPredicate { + ty: clean_middle_ty(bounded_ty, cx, None, None), + bounds, + bound_params: Vec::new(), // FIXME: reconstruct outer binder + }) +} + +fn clean_ty_bound<'tcx>(cx: &mut DocContext<'tcx>, bound: TyBound<'tcx>) -> GenericBound { + match bound { + // FIXME: filter out non-const Destruct + TyBound::Trait(trait_ref, constraints) => { + let constraints = constraints + .into_iter() + .map(|constraint| clean_assoc_item_constraint(cx, constraint)) + .collect(); + + clean_poly_trait_ref_with_constraints(cx, trait_ref, constraints) + } + TyBound::Outlives(bound) => { + // FIXME: expect instead of unwrap + GenericBound::Outlives(clean_middle_region(bound).unwrap()) + } + } +} + +fn clean_assoc_item_constraint<'tcx>( + cx: &mut DocContext<'tcx>, + constraint: Constraint<'tcx>, +) -> AssocItemConstraint { + match constraint { + Constraint::Equality(proj_pred) => AssocItemConstraint { + assoc: projection_to_path_segment( + // FIXME: This needs to be made resilient for `AliasTerm`s that are associated consts. + proj_pred.map_bound(|pred| pred.projection_term.expect_ty(cx.tcx)), + cx, + ), + kind: AssocItemConstraintKind::Equality { + term: clean_middle_term(proj_pred.term(), cx), + }, + }, + Constraint::Bounds(alias_ty, bounds) => AssocItemConstraint { + assoc: projection_to_path_segment(alias_ty, cx), + kind: AssocItemConstraintKind::Bound { + bounds: bounds + .into_iter() + // FIXME: support for nested constraints + .map(|bound| clean_poly_trait_ref_with_constraints(cx, bound, ThinVec::new())) + .collect(), + }, + }, + } +} + +fn clean_re_clause<'tcx>( + bounded_re: ty::Region<'tcx>, + bounds: Vec>, +) -> WherePredicate { + WherePredicate::RegionPredicate { + lifetime: clean_middle_region(bounded_re).unwrap(), // FIXME: expect + // FIXME: expect + bounds: bounds + .into_iter() + .map(|bound| GenericBound::Outlives(clean_middle_region(bound).unwrap())) + .collect(), + } +} + +// FIXME: it's not really a "Bound" bc we duplicate self_ty for poly trait preds +// more appropriately, it's type-reindexed preds, kinda +// FIXME: explain that we have intermediary because relating clean types is slow and imprecise etc. +enum Clause<'tcx> { + BoundedTy { ty: ty::Binder<'tcx, Ty<'tcx>>, bounds: Vec>, span: Span }, // maybe record "outer bound var candidates" + BoundedRe { re: ty::Region<'tcx>, bounds: Vec> }, +} + +enum TyBound<'tcx> { + Trait(ty::PolyTraitRef<'tcx>, Vec>), + Outlives(ty::Region<'tcx>), +} + +// FIXME: bad name +enum Constraint<'tcx> { + Equality(ty::Binder<'tcx, ty::ProjectionPredicate<'tcx>>), + Bounds(ty::Binder<'tcx, ty::AliasTy<'tcx>>, Vec>), +} + +// FIXME(fmease): Better name +// FIXME(fmease): Docs +fn is_instance_of<'tcx>( + tcx: TyCtxt<'tcx>, + child: ty::TraitRef<'tcx>, + trait_: ty::TraitRef<'tcx>, +) -> bool { + if child == trait_ { + return true; + } + // FIXME: these are not the elaborated ones, can you find an example where this + // matters? + let predicates = tcx.super_predicates_of(child.def_id); + debug_assert!(tcx.generics_of(child.def_id).has_self); + predicates + .predicates + .iter() + .filter_map(|(pred, _)| { + let ty::ClauseKind::Trait(pred) = pred.kind().skip_binder() else { + return None; + }; + if pred.trait_ref.self_ty() != tcx.types.self_param { + return None; + } + + Some(ty::EarlyBinder::bind(pred.trait_ref).instantiate(tcx, child.args)) + }) + .any(|child| is_instance_of(tcx, child, trait_)) +} diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs index af61eb6ae8de8..d9c22b5e1bcef 100644 --- a/src/librustdoc/clean/simplify.rs +++ b/src/librustdoc/clean/simplify.rs @@ -11,165 +11,9 @@ //! This module attempts to reconstruct the original where and/or parameter //! bounds by special casing scenarios such as these. Fun! -use rustc_data_structures::fx::FxIndexMap; -use rustc_data_structures::unord::UnordSet; -use rustc_hir::def_id::DefId; -use rustc_middle::ty; use thin_vec::ThinVec; use crate::clean; -use crate::clean::GenericArgs as PP; -use crate::clean::WherePredicate as WP; -use crate::core::DocContext; - -pub(crate) fn where_clauses(cx: &DocContext<'_>, clauses: ThinVec) -> ThinVec { - // First, partition the where clause into its separate components. - // - // We use `FxIndexMap` so that the insertion order is preserved to prevent messing up to - // the order of the generated bounds. - let mut tybounds = FxIndexMap::default(); - let mut lifetimes = Vec::new(); - let mut equalities = Vec::new(); - - for clause in clauses { - match clause { - WP::BoundPredicate { ty, bounds, bound_params } => { - let (b, p): &mut (Vec<_>, Vec<_>) = tybounds.entry(ty).or_default(); - b.extend(bounds); - p.extend(bound_params); - } - WP::RegionPredicate { lifetime, bounds } => { - lifetimes.push((lifetime, bounds)); - } - WP::EqPredicate { lhs, rhs } => equalities.push((lhs, rhs)), - } - } - - // Look for equality predicates on associated types that can be merged into - // general bound predicates. - equalities.retain(|(lhs, rhs)| { - let Some((ty, trait_did, name)) = lhs.projection() else { - return true; - }; - let Some((bounds, _)) = tybounds.get_mut(ty) else { return true }; - merge_bounds(cx, bounds, trait_did, name, rhs) - }); - - // And finally, let's reassemble everything - let mut clauses = ThinVec::with_capacity(lifetimes.len() + tybounds.len() + equalities.len()); - clauses.extend( - lifetimes.into_iter().map(|(lt, bounds)| WP::RegionPredicate { lifetime: lt, bounds }), - ); - clauses.extend(tybounds.into_iter().map(|(ty, (bounds, bound_params))| WP::BoundPredicate { - ty, - bounds, - bound_params, - })); - clauses.extend(equalities.into_iter().map(|(lhs, rhs)| WP::EqPredicate { lhs, rhs })); - clauses -} - -pub(crate) fn merge_bounds( - cx: &clean::DocContext<'_>, - bounds: &mut Vec, - trait_did: DefId, - assoc: clean::PathSegment, - rhs: &clean::Term, -) -> bool { - !bounds.iter_mut().any(|b| { - let trait_ref = match *b { - clean::GenericBound::TraitBound(ref mut tr, _) => tr, - clean::GenericBound::Outlives(..) => return false, - }; - // If this QPath's trait `trait_did` is the same as, or a supertrait - // of, the bound's trait `did` then we can keep going, otherwise - // this is just a plain old equality bound. - if !trait_is_same_or_supertrait(cx, trait_ref.trait_.def_id(), trait_did) { - return false; - } - let last = trait_ref.trait_.segments.last_mut().expect("segments were empty"); - - match last.args { - PP::AngleBracketed { ref mut constraints, .. } => { - constraints.push(clean::AssocItemConstraint { - assoc: assoc.clone(), - kind: clean::AssocItemConstraintKind::Equality { term: rhs.clone() }, - }); - } - PP::Parenthesized { ref mut output, .. } => match output { - Some(o) => assert_eq!(&clean::Term::Type(o.as_ref().clone()), rhs), - None => { - if *rhs != clean::Term::Type(clean::Type::Tuple(Vec::new())) { - *output = Some(Box::new(rhs.ty().unwrap().clone())); - } - } - }, - }; - true - }) -} - -fn trait_is_same_or_supertrait(cx: &DocContext<'_>, child: DefId, trait_: DefId) -> bool { - if child == trait_ { - return true; - } - let predicates = cx.tcx.super_predicates_of(child); - debug_assert!(cx.tcx.generics_of(child).has_self); - let self_ty = cx.tcx.types.self_param; - predicates - .predicates - .iter() - .filter_map(|(pred, _)| { - if let ty::ClauseKind::Trait(pred) = pred.kind().skip_binder() { - if pred.trait_ref.self_ty() == self_ty { Some(pred.def_id()) } else { None } - } else { - None - } - }) - .any(|did| trait_is_same_or_supertrait(cx, did, trait_)) -} - -pub(crate) fn sized_bounds(cx: &mut DocContext<'_>, generics: &mut clean::Generics) { - let mut sized_params = UnordSet::new(); - - // In the surface language, all type parameters except `Self` have an - // implicit `Sized` bound unless removed with `?Sized`. - // However, in the list of where-predicates below, `Sized` appears like a - // normal bound: It's either present (the type is sized) or - // absent (the type might be unsized) but never *maybe* (i.e. `?Sized`). - // - // This is unsuitable for rendering. - // Thus, as a first step remove all `Sized` bounds that should be implicit. - // - // Note that associated types also have an implicit `Sized` bound but we - // don't actually know the set of associated types right here so that - // should be handled when cleaning associated types. - generics.where_predicates.retain(|pred| { - if let WP::BoundPredicate { ty: clean::Generic(param), bounds, .. } = pred - && *param != rustc_span::symbol::kw::SelfUpper - && bounds.iter().any(|b| b.is_sized_bound(cx)) - { - sized_params.insert(*param); - false - } else { - true - } - }); - - // As a final step, go through the type parameters again and insert a - // `?Sized` bound for each one we didn't find to be `Sized`. - for param in &generics.params { - if let clean::GenericParamDefKind::Type { .. } = param.kind - && !sized_params.contains(¶m.name) - { - generics.where_predicates.push(WP::BoundPredicate { - ty: clean::Type::Generic(param.name), - bounds: vec![clean::GenericBound::maybe_sized(cx)], - bound_params: Vec::new(), - }) - } - } -} /// Move bounds that are (likely) directly attached to generic parameters from the where-clause to /// the respective parameter. diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index c4020f2a450bc..ace6d8f2666e5 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1674,14 +1674,6 @@ impl Type { matches!(self, Type::Tuple(v) if v.is_empty()) } - pub(crate) fn projection(&self) -> Option<(&Type, DefId, PathSegment)> { - if let QPath(box QPathData { self_type, trait_, assoc, .. }) = self { - Some((self_type, trait_.as_ref()?.def_id(), assoc.clone())) - } else { - None - } - } - fn inner_def_id(&self, cache: Option<&Cache>) -> Option { let t: PrimitiveType = match *self { Type::Path { ref path } => return Some(path.def_id()), @@ -2373,12 +2365,6 @@ pub(crate) enum Term { Constant(Constant), } -impl Term { - pub(crate) fn ty(&self) -> Option<&Type> { - if let Term::Type(ty) = self { Some(ty) } else { None } - } -} - impl From for Term { fn from(ty: Type) -> Self { Term::Type(ty)