From a5edf955062ea3a53d070ead8a5e430ea4c2349d Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Sun, 11 Oct 2026 15:13:32 +0000 Subject: [PATCH] Support a bit more types on rustdoc auto traits --- .../src/traits/auto_trait.rs | 262 ++++++++++++++++-- 1 file changed, 239 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/auto_trait.rs b/compiler/rustc_trait_selection/src/traits/auto_trait.rs index 3b60bc53141d5..9c623b509e8d3 100644 --- a/compiler/rustc_trait_selection/src/traits/auto_trait.rs +++ b/compiler/rustc_trait_selection/src/traits/auto_trait.rs @@ -4,10 +4,10 @@ use std::collections::VecDeque; use std::iter; -use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet, IndexEntry}; use rustc_data_structures::unord::UnordSet; use rustc_hir::def_id::CRATE_DEF_ID; -use rustc_infer::infer::DefineOpaqueTypes; +use rustc_infer::infer::{DefineOpaqueTypes, RegionVariableOrigin}; use rustc_middle::ty::{Region, RegionVid}; use rustc_span::DUMMY_SP; use tracing::debug; @@ -196,11 +196,11 @@ impl<'tcx> AutoTraitFinder<'tcx> { // When the new solver is enabled globally we keep things deliberately // simple. The precise auto-trait synthesis depends on old-solver // internals, so here we only synthesize a simple field-based auto-trait - // impl for ADTs. + // impl for ADTs, primitives and some simple types such as tuples. // - // If the self type is not an ADT we return `NoImpl` instead of trying + // If the self type is none of those we return `NoImpl` instead of trying // to do anything fancy. To decide whether to emit a negative impl, we - // replace the ADT's generic arguments with inference variables and + // replace the ty's generic arguments with inference variables and // check whether the auto trait can hold. A true error from that probe // becomes a `NegativeImpl`, otherwise we continue on to emit the // imprecise field-based impl. @@ -208,10 +208,45 @@ impl<'tcx> AutoTraitFinder<'tcx> { // This keeps rustdoc from ICE-ing while `-Znext-solver=globally` is // used for testing, even if the generated synthetic impls are less // precise. + match *ty.kind() { + // Uninteresting or shouldn't be encountered types. + // Perhaps we'd like to assert on the later, but since this is + // a makeshift hack so let's not break things. + ty::Foreign(_) + | ty::FnDef(_, _) + | ty::UnsafeBinder(_) + | ty::Dynamic(_, _) + | ty::Closure(_, _) + | ty::CoroutineClosure(_, _) + | ty::Coroutine(_, _) + | ty::CoroutineWitness(_, _) + | ty::Bound(_, _) + | ty::Placeholder(_) + | ty::Infer(_) + | ty::Error(_) + | ty::Param(_) => { + return AutoTraitResult::NoImpl; + } + + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Adt(_, _) + | ty::Str + | ty::Array(_, _) + | ty::Pat(_, _) + | ty::Slice(_) + | ty::RawPtr(_, _) + | ty::Ref(_, _, _) + | ty::FnPtr(_, _) + | ty::Never + | ty::Tuple(_) + | ty::Alias(_, _) => {} + } + let tcx = self.tcx; - let ty::Adt(adt_def, args) = *ty.kind() else { - return AutoTraitResult::NoImpl; - }; let mut disqualifying_impl = None; tcx.for_each_relevant_impl(trait_did, ty, |impl_def_id| { @@ -226,22 +261,39 @@ impl<'tcx> AutoTraitFinder<'tcx> { } let (infcx, orig_env) = tcx.infer_ctxt().build_with_typing_env(typing_env); - let field_clauses = adt_def - .all_fields() - .map(|field| field.ty(tcx, args).skip_norm_wip()) - .filter(|field_ty| field_ty.has_non_region_param()) - .map(|field_ty| { - ty::TraitClause { - trait_ref: ty::TraitRef::new(tcx, trait_did, [field_ty]), - polarity: ty::ClausePolarity::Positive, - } - .upcast(tcx) - }) - .collect::>>(); - let full_user_env = ty::ParamEnv::new(tcx, orig_env.caller_bounds().chain(field_clauses)); + let fresh_ty = if !ty.has_param() { + ty + } else { + let mut tys = FxHashMap::default(); + let mut regions = FxHashMap::default(); + let mut consts = FxHashMap::default(); - let fresh_args = infcx.fresh_args_for_item(DUMMY_SP, adt_def.did()); - let fresh_ty = ty::EarlyBinder::bind(tcx, ty).instantiate(tcx, fresh_args).skip_norm_wip(); + ty.fold_with(&mut ty::BottomUpFolder { + tcx, + ty_op: |ty| { + let ty::Param(_) = *ty.kind() else { + return ty; + }; + *tys.entry(ty).or_insert_with(|| infcx.next_ty_var(DUMMY_SP)) + }, + lt_op: |lt| { + let ty::ReEarlyParam(param) = lt.kind() else { + return lt; + }; + *regions.entry(lt).or_insert_with(|| { + infcx.next_region_var(RegionVariableOrigin::RegionParameterDefinition( + DUMMY_SP, param.name, + )) + }) + }, + ct_op: |ct| { + let ty::ConstKind::Param(_) = ct.kind() else { + return ct; + }; + *consts.entry(ct).or_insert_with(|| infcx.next_const_var(DUMMY_SP)) + }, + }) + }; let ocx = ObligationCtxt::new(&infcx); ocx.register_bound(ObligationCause::dummy(), orig_env, fresh_ty, trait_did); let errors = ocx.try_evaluate_obligations(); @@ -249,6 +301,170 @@ impl<'tcx> AutoTraitFinder<'tcx> { return AutoTraitResult::NegativeImpl; } + /// Gather types that needs to be the RHS of the where bounds for auto trait impls, + /// by recursing into each field type. This is a recursive function rather than a + /// `TypeVisitor` because we have to look into nested fields. + fn gather_constituent_tys<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + trait_did: DefId, + constituents: &mut Vec>, + visited: &mut FxHashSet>, + depth: usize, + ) { + if !tcx.recursion_limit().value_within_limit(depth) { + return; + } + + if !visited.insert(ty) { + return; + } + + let depth = depth + 1; + + // If the type has any explicit impl for the trait, we just add a + // `where {type}: Trait` bound instead of recursing into its nested fields. + let mut has_disqualifying_impl = false; + tcx.for_each_relevant_impl(trait_did, ty, |_| { + has_disqualifying_impl = true; + }); + if has_disqualifying_impl { + constituents.push(ty); + return; + } + + match *ty.kind() { + ty::Param(_) => { + constituents.push(ty); + } + + // Primitive types which don't need to consider generics + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Never + | ty::Str + | ty::FnPtr(_, _) => {} + + ty::Adt(adt_def, args) => { + // For `PhantomData`, we pass `T`. + if adt_def.is_phantom_data() { + gather_constituent_tys( + tcx, + args.type_at(0), + trait_did, + constituents, + visited, + depth, + ); + return; + } + + for field_ty in + adt_def.all_fields().map(|field| field.ty(tcx, args).skip_norm_wip()) + { + gather_constituent_tys( + tcx, + field_ty, + trait_did, + constituents, + visited, + depth, + ); + } + } + + ty::RawPtr(elem, _) | ty::Ref(_, elem, _) => { + gather_constituent_tys(tcx, elem, trait_did, constituents, visited, depth); + } + + ty::Array(elem, _) | ty::Pat(elem, _) | ty::Slice(elem) => { + gather_constituent_tys(tcx, elem, trait_did, constituents, visited, depth); + } + ty::Tuple(tys) => { + for elem in tys { + gather_constituent_tys(tcx, elem, trait_did, constituents, visited, depth); + } + } + + // Aliases can be mentioned in fields + ty::Alias(_, _) => { + constituents.push(ty); + } + + ty::Foreign(_) + | ty::FnDef(_, _) + | ty::UnsafeBinder(_) + | ty::Dynamic(_, _) + | ty::Closure(_, _) + | ty::CoroutineClosure(_, _) + | ty::Coroutine(_, _) + | ty::CoroutineWitness(_, _) + | ty::Bound(_, _) + | ty::Placeholder(_) + | ty::Infer(_) + | ty::Error(_) => {} + } + } + + let mut constituent_tys = vec![]; + gather_constituent_tys( + tcx, + ty, + trait_did, + &mut constituent_tys, + &mut Default::default(), + 0, + ); + + let constituent_clauses = constituent_tys + .into_iter() + .map(|constituent_ty| { + ty::TraitClause { + trait_ref: ty::TraitRef::new(tcx, trait_did, [constituent_ty]), + polarity: ty::ClausePolarity::Positive, + } + .upcast(tcx) + }) + .collect::>>(); + let full_user_env = + ty::ParamEnv::new(tcx, orig_env.caller_bounds().chain(constituent_clauses)); + let full_user_env = if full_user_env.has_aliases() { + // We already have `normalize_param_env_or_error` and this does + // the similar thing. But we cannot use it because.. + // + // - `normalize_param_env_or_error` elaborates the clauses but + // we don't want to do that here. + // - Instead of erroring, we'd like to just fallback to + // unnormalized clauses. + // + // And `normalize_param_env_or_error` normalizes outlives clauses + // separately to avoid lots of inferred outlives predicates on + // assoc types causing problems but we don't need to do that because + // we don't elaborate the clauses to be normalized. + let elaborated_env: Vec<_> = + util::elaborate(tcx, full_user_env.caller_bounds()).collect(); + let elaborated_env = ty::set_type_aliases_to_rigid(tcx, elaborated_env); + let elaborated_env = + ty::ParamEnv::new(tcx, set_projection_term_to_non_rigid(tcx, elaborated_env)); + let infcx = tcx.infer_ctxt().ignoring_regions().build(TypingMode::non_body_analysis()); + let ocx = ObligationCtxt::new(&infcx); + let normalized = ocx.normalize( + &ObligationCause::dummy(), + elaborated_env, + Unnormalized::new_wip(full_user_env), + ); + + let errors = ocx.evaluate_obligations_error_on_ambiguity(); + let normalized = infcx.deeply_resolve_ignoring_regions(normalized); + + if errors.has_errors() || normalized.has_infer() { full_user_env } else { normalized } + } else { + full_user_env + }; + let info = AutoTraitInfo { full_user_env, region_data: RegionConstraintData::default(),