From f14760e9fec42b13d11aa7c072e5d60721bd2d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Mon, 3 Jun 2024 03:22:35 +0200 Subject: [PATCH] rustdoc: rewrite blanket impl synthesis --- src/librustdoc/clean/blanket_impl.rs | 209 +++++++++++++-------------- 1 file changed, 101 insertions(+), 108 deletions(-) diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs index 8dda831cac85b..9f21292d43a91 100644 --- a/src/librustdoc/clean/blanket_impl.rs +++ b/src/librustdoc/clean/blanket_impl.rs @@ -1,12 +1,14 @@ +use std::ops::ControlFlow; + use rustc_data_structures::thin_vec::ThinVec; use rustc_hir as hir; -use rustc_infer::infer::{DefineOpaqueTypes, InferOk, TyCtxtInferExt}; -use rustc_infer::traits; -use rustc_middle::ty::{self, TypingMode, Unnormalized, Upcast}; -use rustc_span::DUMMY_SP; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_middle::ty; use rustc_span::def_id::DefId; -use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; -use tracing::{debug, instrument, trace}; +use rustc_trait_selection::solve::CandidateSource; +use rustc_trait_selection::solve::inspect::{ + InferCtxtProofTreeExt as _, InspectGoal, ProbeKind, ProofTreeVisitor, +}; use crate::clean; use crate::clean::{ @@ -14,122 +16,113 @@ use crate::clean::{ }; use crate::core::DocContext; -#[instrument(level = "debug", skip(cx))] +#[tracing::instrument(level = "debug", skip(cx))] pub(crate) fn synthesize_blanket_impls( cx: &mut DocContext<'_>, item_def_id: DefId, ) -> Vec { let tcx = cx.tcx; - let ty = tcx.type_of(item_def_id); + let item_ty = tcx.type_of(item_def_id); + let param_env = ty::ParamEnv::empty(); + let typing_mode = ty::TypingMode::non_body_analysis(); let mut blanket_impls = Vec::new(); + for trait_def_id in tcx.visible_traits() { if !cx.cache.effective_visibilities.is_reachable(tcx, trait_def_id) - || cx.generated_synthetics.contains(&(ty.skip_binder(), trait_def_id)) + || cx.generated_synthetics.contains(&(item_ty.skip_binder(), trait_def_id)) { continue; } - // NOTE: doesn't use `for_each_relevant_impl` to avoid looking at anything besides blanket impls - let trait_impls = tcx.trait_impls_of(trait_def_id); - 'blanket_impls: for &impl_def_id in trait_impls.blanket_impls() { - trace!("considering impl `{impl_def_id:?}` for trait `{trait_def_id:?}`"); - - let trait_ref = tcx.impl_trait_ref(impl_def_id); - if !matches!(trait_ref.skip_binder().self_ty().kind(), ty::Param(_)) { - continue; - } - let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); - let args = infcx.fresh_args_for_item(DUMMY_SP, item_def_id); - let impl_ty = ty.instantiate(tcx, args).skip_norm_wip(); - let param_env = ty::ParamEnv::empty(); - - let impl_args = infcx.fresh_args_for_item(DUMMY_SP, impl_def_id); - let impl_trait_ref = trait_ref.instantiate(tcx, impl_args).skip_norm_wip(); - - // Require the type the impl is implemented on to match - // our type, and ignore the impl if there was a mismatch. - let Ok(eq_result) = infcx.at(&traits::ObligationCause::dummy(), param_env).eq( - DefineOpaqueTypes::Yes, - impl_trait_ref.self_ty(), - impl_ty, - ) else { - continue; - }; - let InferOk { value: (), obligations } = eq_result; - // FIXME(eddyb) ignoring `obligations` might cause false positives. - drop(obligations); - - let predicates = tcx - .predicates_of(impl_def_id) - .instantiate(tcx, impl_args) - .predicates - .into_iter() - .map(Unnormalized::skip_norm_wip) - .chain(Some(impl_trait_ref.upcast(tcx))); - for predicate in predicates { - let obligation = traits::Obligation::new( - tcx, - traits::ObligationCause::dummy(), - param_env, - predicate, - ); - match infcx.evaluate_obligation(&obligation) { - Ok(eval_result) if eval_result.may_apply() => {} - Err(traits::OverflowError::Canonical) => {} - _ => continue 'blanket_impls, - } - } - debug!("found applicable impl for trait ref {trait_ref:?}"); - - cx.generated_synthetics.insert((ty.skip_binder(), trait_def_id)); - - blanket_impls.push(clean::Item { - inner: Box::new(clean::ItemInner { - name: None, - item_id: clean::ItemId::Blanket { impl_id: impl_def_id, for_: item_def_id }, - attrs: Default::default(), - stability: None, - kind: clean::ImplItem(Box::new(clean::Impl { - safety: hir::Safety::Safe, - generics: clean_ty_generics(cx, impl_def_id), - // FIXME(eddyb) compute both `trait_` and `for_` from - // the post-inference `trait_ref`, as it's more accurate. - trait_: Some(clean_trait_ref_with_constraints( - cx, - ty::Binder::dummy(trait_ref.instantiate_identity().skip_norm_wip()), - ThinVec::new(), - )), - for_: clean_middle_ty( - ty::Binder::dummy(ty.instantiate_identity().skip_norm_wip()), - cx, - None, - None, - ), - items: tcx - .associated_items(impl_def_id) - .in_definition_order() - .filter(|item| !item.is_impl_trait_in_trait()) - .map(|item| clean_middle_assoc_item(item, cx)) - .collect(), - polarity: ty::ImplPolarity::Positive, - kind: clean::ImplKind::Blanket(Box::new(clean_middle_ty( - ty::Binder::dummy( - trait_ref.instantiate_identity().skip_norm_wip().self_ty(), - ), - cx, - None, - None, - ))), - is_deprecated: tcx - .lookup_deprecation(impl_def_id) - .is_some_and(|deprecation| deprecation.is_in_effect()), - })), - cfg: None, - inline_stmt_id: None, - }), - }); + + // FIXME(fmease): ... + if tcx.is_lang_item(trait_def_id, hir::LangItem::PointeeSized) { + continue; } + + let infcx = tcx.infer_ctxt().with_next_trait_solver(true).build(typing_mode); + + let item_args = infcx.fresh_args_for_item(rustc_span::DUMMY_SP, item_def_id); + let self_ty = item_ty.instantiate(tcx, item_args).skip_normalization(); + let trait_args = infcx.fresh_args_for_item(rustc_span::DUMMY_SP, trait_def_id); + let trait_ref = ty::TraitRef::new_from_args(tcx, trait_def_id, trait_args) + .with_replaced_self_ty(tcx, self_ty); + let goal = ty::solve::Goal::new(tcx, param_env, trait_ref); + + let ControlFlow::Break(impl_def_id) = infcx.visit_proof_tree(goal, &mut HasBlanketImpl) + else { + continue; + }; + + cx.generated_synthetics.insert((item_ty.skip_binder(), trait_def_id)); + + let item_ty = item_ty.instantiate_identity().skip_normalization(); + let impl_trait_ref = + tcx.impl_trait_ref(impl_def_id).instantiate_identity().skip_normalization(); + + blanket_impls.push(clean::Item { + inner: Box::new(clean::ItemInner { + name: None, + item_id: clean::ItemId::Blanket { impl_id: impl_def_id, for_: item_def_id }, + attrs: Default::default(), + stability: None, + kind: clean::ImplItem(Box::new(clean::Impl { + safety: hir::Safety::Safe, + generics: clean_ty_generics(cx, impl_def_id), + // FIXME(eddyb) compute both `trait_` and `for_` from + // the post-inference `trait_ref`, as it's more accurate. + trait_: Some(clean_trait_ref_with_constraints( + cx, + ty::Binder::dummy(impl_trait_ref), + ThinVec::new(), + )), + for_: clean_middle_ty(ty::Binder::dummy(item_ty), cx, None, None), + items: tcx + .associated_items(impl_def_id) + .in_definition_order() + .filter(|item| !item.is_impl_trait_in_trait()) + .map(|item| clean_middle_assoc_item(item, cx)) + .collect(), + polarity: ty::ImplPolarity::Positive, + kind: clean::ImplKind::Blanket(Box::new(clean_middle_ty( + ty::Binder::dummy(impl_trait_ref.self_ty()), + cx, + None, + None, + ))), + is_deprecated: tcx + .lookup_deprecation(impl_def_id) + .is_some_and(|deprecation| deprecation.is_in_effect()), + })), + cfg: None, + inline_stmt_id: None, + }), + }); } blanket_impls } + +struct HasBlanketImpl; + +impl<'tcx> ProofTreeVisitor<'tcx> for HasBlanketImpl { + type Result = ControlFlow; + + fn span(&self) -> rustc_span::Span { + rustc_span::DUMMY_SP + } + + fn visit_goal(&mut self, goal: &InspectGoal<'_, 'tcx>) -> Self::Result { + for candidate in goal.candidates() { + if candidate.result().is_ok() + && let ProbeKind::TraitCandidate { source, .. } = candidate.kind() + && let CandidateSource::Impl(impl_def_id) = source + && let ty::Param(_) = goal.infcx().tcx.type_of(impl_def_id).skip_binder().kind() + { + return ControlFlow::Break(impl_def_id); + } + } + + ControlFlow::Continue(()) + } +}