diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index f46ffea830e68..2d41d2605c587 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -9,8 +9,8 @@ use rustc_trait_selection::traits::auto_trait::{self, RegionTarget}; use thin_vec::ThinVec; use crate::clean::{ - self, clean_generic_param_def, clean_middle_ty, clean_predicate, - clean_trait_ref_with_constraints, clean_ty_generics, simplify, Lifetime, + self, clean_generic_param_def, clean_middle_ty, clean_trait_ref_with_constraints, + clean_ty_generics, Lifetime, }; use crate::core::DocContext; @@ -167,7 +167,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: @@ -192,14 +192,15 @@ 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, &mut super::modern::WhereClause::default()); + // 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 cffadc7c10a91..2f5550066d0ca 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -25,6 +25,8 @@ 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; @@ -350,73 +352,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>, @@ -437,24 +372,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>, @@ -491,6 +408,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>, @@ -780,19 +698,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 { @@ -800,7 +718,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 @@ -810,115 +728,84 @@ 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(); + struct ApitFinder<'a, 'tcx> { + apits: &'a BTreeMap, rustc_span::Span)>>, + } - 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); - } + impl<'tcx> ty::TypeVisitor> for ApitFinder<'_, 'tcx> { + type Result = ControlFlow; + + 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); } - _ => (), } + _ if ty.has_param() => return ty.super_visit_with(self), + _ => {} + } + ControlFlow::Continue(()) + } + } - None - })(); - - if let Some(param_idx) = param_idx - && let Some(bounds) = impl_trait.get_mut(¶m_idx) + // FIXME: explainer + let predicates = predicates + .predicates + .iter() + .copied() + .filter(|&(predicate, span)| { + if let ControlFlow::Break(index) = + predicate.visit_with(&mut ApitFinder { apits: &apits }) { - let pred = clean_predicate(*pred, cx)?; - - bounds.extend(pred.get_bounds().into_iter().flatten().cloned()); - - 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), - )); - } - - return None; + apits.get_mut(&index).unwrap().push((predicate, span)); + return false; } - - Some(pred) + true }) - .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)); - } + .collect(); - // Move trait bounds to the front. - bounds.sort_by_key(|b| !b.is_trait_bound()); + // 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, &mut modern::Apit::default()); + let mut bounds = match where_predicates.pop() { + Some(WherePredicate::BoundPredicate { bounds, .. }) => bounds, + Some(_) => unreachable!(), + None => Vec::new(), + }; // 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()) { + if bounds.iter().all(|bound| !bound.is_trait_bound()) { bounds.insert(0, GenericBound::sized(cx)); } + cx.impl_trait_bounds.insert(index.into(), bounds); + } - 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); - } - } + let mut cleaner = modern::WhereClause::default(); + let mut where_predicates = modern::clean_predicates(cx, predicates, &mut cleaner); - cx.impl_trait_bounds.insert(idx.into(), bounds); + // FIXME: This adds extra clauses, instead of modifying existing bounds + // Smh. make clean_preds add those bounds onto existing ones if available + // NOTE: Maybe we should just cave in an add an `Option` param to clean_preds + // FIXME: This is so stupid + for param in &generics.own_params { + if let ty::GenericParamDefKind::Type { synthetic: false, .. } = param.kind + && !cleaner.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(), + }) + } } - // 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 + Generics { params, where_predicates } } fn clean_ty_alias_inner_type<'tcx>( @@ -1376,6 +1263,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( TyMethodItem(item) } } + // FIXME: Rewrite this!!! ty::AssocKind::Type => { let my_name = assoc_item.name; @@ -2132,7 +2020,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( @@ -2168,7 +2057,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) @@ -2275,78 +2164,40 @@ fn clean_middle_opaque_bounds<'tcx>( impl_trait_def_id: DefId, args: ty::GenericArgsRef<'tcx>, ) -> Type { - let mut has_sized = false; - - let bounds: Vec<_> = cx + let predicates: 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).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) + // FIXME: Make it so clean_pred inserts `Sized` before any outlives bounds + let mut where_predicates = + modern::clean_predicates(cx, predicates, &mut modern::OpaqueTy::default()); + let mut bounds = match where_predicates.pop() { + Some(WherePredicate::BoundPredicate { bounds, .. }) => bounds, + Some(_) => unreachable!(), + None => Vec::new(), + }; - 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()); + // // 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()) { + if bounds.iter().all(|bound| !bound.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.to_vec())); } + // ImplTrait(bounds) } diff --git a/src/librustdoc/clean/modern.rs b/src/librustdoc/clean/modern.rs new file mode 100644 index 0000000000000..12b7cbae8ddaf --- /dev/null +++ b/src/librustdoc/clean/modern.rs @@ -0,0 +1,392 @@ +#![allow(dead_code)] // FIXME + +use rustc_data_structures::unord::UnordSet; +use rustc_middle::ty::{self, ToPolyTraitRef, Ty, TyCtxt}; +use rustc_span::symbol::kw; +use rustc_span::Span; +use thin_vec::ThinVec; + +use super::{ + clean_middle_region, clean_middle_term, clean_middle_ty, clean_poly_trait_ref_with_constraints, + projection_to_path_segment, AssocItemConstraint, AssocItemConstraintKind, GenericBound, + WherePredicate, +}; +use crate::core::DocContext; + +// FIXME(return_type_notation, #123996): Support RTN. +// FIXME(async_closure): De-lower `AsyncFn*` bounds to `async Fn*` ones. + +// FIXME: experiment +pub(super) trait CleanPredicate<'tcx> { + fn elide_sized_bound_on(&mut self, ty: ty::Binder<'tcx, Ty<'tcx>>) -> bool; +} + +////////////// FIXME: temporary location + +#[derive(Default)] +pub(super) struct WhereClause { + pub(super) sized: UnordSet, +} + +impl<'tcx> CleanPredicate<'tcx> for WhereClause { + fn elide_sized_bound_on(&mut self, ty: ty::Binder<'tcx, Ty<'tcx>>) -> bool { + if let ty::Param(param_ty) = *ty.skip_binder().kind() + && param_ty.name != kw::SelfUpper + { + self.sized.insert(param_ty.index); + return true; + } + false + } +} + +#[derive(Default)] +pub(super) struct Apit { + sized: bool, +} + +impl<'tcx> CleanPredicate<'tcx> for Apit { + fn elide_sized_bound_on(&mut self, ty: ty::Binder<'tcx, Ty<'tcx>>) -> bool { + if let ty::Param(_) = ty.skip_binder().kind() { + self.sized = true; + return true; + } + false + } +} + +#[derive(Default)] +pub(super) struct OpaqueTy { + sized: bool, +} + +impl<'tcx> CleanPredicate<'tcx> for OpaqueTy { + fn elide_sized_bound_on(&mut self, ty: ty::Binder<'tcx, Ty<'tcx>>) -> bool { + // FIXME: is this correct? + if let ty::Alias(ty::Opaque, _) = ty.skip_binder().kind() { + eprintln!("HIT!"); + self.sized = true; + return true; + } + false + } +} + +////////////// + +// FIXME: we overflow the stack on RPITITs :( + +// FIXME: We 'need' to support arbitrarily nested ATBs +// like `A>` + +pub(super) fn clean_predicates<'tcx, C: CleanPredicate<'tcx>>( + cx: &mut DocContext<'tcx>, + mut predicates: Vec<(ty::Clause<'tcx>, Span)>, + cleaner: &mut C, +) -> 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(); + + for (predicate, span) in predicates { + group_predicate(cx.tcx, predicate, span, &mut clauses, cleaner); + } + + // 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, cleaner), + Clause::BoundedRe { re, bounds } => Some(clean_re_clause(re, bounds)), + }) + .collect() +} + +// FIXME: horrendous name +fn group_predicate<'tcx, C: CleanPredicate<'tcx>>( + tcx: TyCtxt<'tcx>, + predicate: ty::Clause<'tcx>, + span: Span, + clauses: &mut Vec>, // FIXME: ugly out param + cleaner: &mut C, +) { + 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(); + + // FIXME: properly implement this + if tcx.is_lang_item(pred.def_id(), rustc_hir::LangItem::Sized) + && cleaner.elide_sized_bound_on(bounded_ty) + { + // 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). + // FIXME: Add back explainer about implicit Sized from mod simplify + return; + } + + // FIXME: I don't think this can handle `Trait>` (namely `Inner == ()`) + // FIXME: does this scale to assoc tys? we might want to allow the caller to specify + // the "base bounded ty" (Param for Normal&APIT, Alias(Opaque) for RPIT(IT?), Alias(Projection) for assoc ty item bounds) + if let ty::Alias(ty::Projection, alias_ty) = *bounded_ty.skip_binder().kind() + && let Some(Clause::BoundedTy { bounds, span: clause_span, .. }) = clauses.last_mut() + && clause_span.contains(span) // FIXME: explainer + && let Some(Bound::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(), alias_ty.trait_ref(tcx)) + { + // FIXME: support constraints on this bound + let bound = Bound::Trait(pred.to_poly_trait_ref(), Vec::new()); + let bounded_alias_ty = pred.rebind(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 //FIXME bad naming left<->right (both are bounded) + { + 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 = Bound::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 it's fine to ignore polarity + if let Some(Clause::BoundedTy { bounds, .. }) = clauses.last_mut() + && let Some(Bound::Trait(trait_ref, constraints)) = bounds.last_mut() + // FIXME: Explain why this seems to be necessary + // FIXME: is skip_binder ok?? feels fishy + && 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 ty::Alias(ty::Projection, alias_ty) = *bounded_ty.skip_binder().kind() + && let Some(Clause::BoundedTy { bounds, span: clause_span, .. }) = clauses.last_mut() + && clause_span.contains(span) // FIXME: explainer + && let Some(Bound::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(), alias_ty.trait_ref(tcx)) + { + let bound = Bound::Outlives(bound); + let bounded_alias_ty = bounded_ty.rebind(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 //FIXME bad naming left<->right (both are bounded) + { + alias_bounds.push(bound); + } else { + constraints.push(Constraint::Bounds(bounded_alias_ty, vec![bound])); + } + return; + } + + if let Some(Clause::BoundedTy { ty, bounds, .. }) = clauses.last_mut() + && *ty == bounded_ty // FIXME: explain why `==` is okay here + { + bounds.push(Bound::Outlives(bound)); + } else { + clauses.push(Clause::BoundedTy { ty: bounded_ty, bounds: vec![Bound::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, C: CleanPredicate<'tcx>>( + cx: &mut DocContext<'tcx>, + bounded_ty: ty::Binder<'tcx, Ty<'tcx>>, + bounds: Vec>, + _cleaner: &mut C, // FIXME +) -> Option { + let bounds: Vec<_> = bounds.into_iter().map(|bound| clean_bound(cx, bound)).collect(); + + // FIXME: doing this in here isn't great... bc this leads to duplicate efforts + // if we don't have any preds (e.g., `X`) where we need to go through all + // "generics" "anyway" (NB: this module doesn't have a notion of "generics"!!!) + // 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_bound<'tcx>(cx: &mut DocContext<'tcx>, bound: Bound<'tcx>) -> GenericBound { + match bound { + // FIXME: filter out non-const Destruct + Bound::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) + } + Bound::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().map(|bound| clean_bound(cx, bound)).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 Bound<'tcx> { + Trait(ty::PolyTraitRef<'tcx>, Vec>), + Outlives(ty::Region<'tcx>), +} + +// FIXME: bad name, I guess +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; + } + + debug_assert!(tcx.generics_of(child.def_id).has_self); + + // FIXME: these are not the elaborated ones, can you find an example where this matters? + tcx.explicit_super_predicates_of(child.def_id) + .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 1d81ae3eb8ba7..d9c22b5e1bcef 100644 --- a/src/librustdoc/clean/simplify.rs +++ b/src/librustdoc/clean/simplify.rs @@ -11,163 +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, 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(..) | clean::GenericBound::Use(_) => 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.explicit_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 - && 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 4850500a1bfae..5a6360c563eb7 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1654,14 +1654,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 - } - } - /// Use this method to get the [DefId] of a [clean] AST node, including [PrimitiveType]s. /// /// [clean]: crate::clean @@ -2345,12 +2337,6 @@ pub(crate) enum Term { Constant(ConstantKind), } -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)