Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 239 additions & 23 deletions compiler/rustc_trait_selection/src/traits/auto_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -196,22 +196,57 @@ 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.
//
// 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| {
Expand All @@ -226,29 +261,210 @@ 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::<Vec<ty::Clause<'tcx>>>();
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();
if !errors.no_errors() {
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<Ty<'tcx>>,
visited: &mut FxHashSet<Ty<'tcx>>,
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<T>`, 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::<Vec<ty::Clause<'tcx>>>();
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(),
Expand Down
Loading