Skip to content

Commit 1a27a84

Browse files
committed
Implement unification of const abstract impls
If two impls cover the entire set of possibilities: i.e. ``` impl TraitName for Struct<true> {} impl TraitName for Struct<false> {} ``` Then it would be expected that: ``` ... <const N: usize> ... where Struct<{N > 1}>: TraitName { ``` Should compile. This allows for such by generating a pseudo-impl for conditions which are exhaustive. Since these impls will never be instantiated as is, it is fine to have a fake impl. For now, this checks for a single bool condition, and whether both bool implementations are present. Change some of the api surface This adds separate functionality for checking for recursive traits versus exhaustive traits. But is still a WIP.
1 parent 866710c commit 1a27a84

21 files changed

+530
-2
lines changed

compiler/rustc_feature/src/active.rs

+2
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,8 @@ declare_features! (
430430
(active, half_open_range_patterns_in_slices, "1.66.0", Some(67264), None),
431431
/// Allows `if let` guard in match arms.
432432
(active, if_let_guard, "1.47.0", Some(51114), None),
433+
/// Allow multiple const-generic impls to unify for traits which are abstract.
434+
(active, impl_exhaustive_const_traits, "1.65.0", Some(104806), None),
433435
/// Allows `impl Trait` to be used inside associated types (RFC 2515).
434436
(active, impl_trait_in_assoc_type, "1.70.0", Some(63063), None),
435437
/// Allows `impl Trait` as output type in `Fn` traits in return position of functions.

compiler/rustc_middle/src/traits/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -653,27 +653,32 @@ pub enum ImplSource<'tcx, N> {
653653

654654
/// Successful resolution for a builtin impl.
655655
Builtin(BuiltinImplSource, Vec<N>),
656+
/// Impl Source for an Abstract Const unification.
657+
Exhaustive(Vec<N>),
656658
}
657659

658660
impl<'tcx, N> ImplSource<'tcx, N> {
659661
pub fn nested_obligations(self) -> Vec<N> {
660662
match self {
661663
ImplSource::UserDefined(i) => i.nested,
662664
ImplSource::Param(_, n) | ImplSource::Builtin(_, n) => n,
665+
ImplSource::Exhaustive(n) => n,
663666
}
664667
}
665668

666669
pub fn borrow_nested_obligations(&self) -> &[N] {
667670
match self {
668671
ImplSource::UserDefined(i) => &i.nested,
669672
ImplSource::Param(_, n) | ImplSource::Builtin(_, n) => &n,
673+
ImplSource::Exhaustive(n) => &n,
670674
}
671675
}
672676

673677
pub fn borrow_nested_obligations_mut(&mut self) -> &mut [N] {
674678
match self {
675679
ImplSource::UserDefined(i) => &mut i.nested,
676680
ImplSource::Param(_, n) | ImplSource::Builtin(_, n) => n,
681+
ImplSource::Exhaustive(ref mut n) => n,
677682
}
678683
}
679684

@@ -691,6 +696,7 @@ impl<'tcx, N> ImplSource<'tcx, N> {
691696
ImplSource::Builtin(source, n) => {
692697
ImplSource::Builtin(source, n.into_iter().map(f).collect())
693698
}
699+
ImplSource::Exhaustive(n) => ImplSource::Exhaustive(n.into_iter().map(f).collect()),
694700
}
695701
}
696702
}

compiler/rustc_middle/src/traits/select.rs

+5
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ pub enum SelectionCandidate<'tcx> {
167167

168168
/// Implementation of `const Destruct`, optionally from a custom `impl const Drop`.
169169
ConstDestructCandidate(Option<DefId>),
170+
171+
/// Candidate which is generated for a abstract const, unifying other impls if they
172+
/// exhaustively cover all values for a type.
173+
/// Will never actually be used, by construction.
174+
ExhaustiveCandidate(ty::PolyTraitPredicate<'tcx>),
170175
}
171176

172177
/// The result of trait evaluation. The order is important

compiler/rustc_middle/src/traits/structural_impls.rs

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ impl<'tcx, N: fmt::Debug> fmt::Debug for traits::ImplSource<'tcx, N> {
1616
super::ImplSource::Param(ct, n) => {
1717
write!(f, "ImplSourceParamData({n:?}, {ct:?})")
1818
}
19+
super::ImplSource::Exhaustive(ref n) => write!(f, "Exhaustive({:?})", n),
1920
}
2021
}
2122
}

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,7 @@ symbols! {
815815
if_let_guard,
816816
if_while_or_patterns,
817817
ignore,
818+
impl_exhaustive_const_traits,
818819
impl_header_lifetime_elision,
819820
impl_lint_pass,
820821
impl_trait_in_assoc_type,

compiler/rustc_trait_selection/src/traits/project.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1926,6 +1926,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
19261926
// why we special case object types.
19271927
false
19281928
}
1929+
ImplSource::Exhaustive(..) => false,
19291930
ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
19301931
| ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
19311932
// These traits have no associated types.
@@ -2007,6 +2008,7 @@ fn confirm_select_candidate<'cx, 'tcx>(
20072008
}
20082009
ImplSource::Builtin(BuiltinImplSource::Object { .. }, _)
20092010
| ImplSource::Param(..)
2011+
| ImplSource::Exhaustive(..)
20102012
| ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
20112013
| ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => {
20122014
// we don't create Select candidates with this kind of resolution

compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs

+56-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_hir as hir;
1212
use rustc_infer::traits::ObligationCause;
1313
use rustc_infer::traits::{Obligation, PolyTraitObligation, SelectionError};
1414
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
15-
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
15+
use rustc_middle::ty::{self, ConstKind, Ty, TypeVisitableExt};
1616

1717
use crate::traits;
1818
use crate::traits::query::evaluate_obligation::InferCtxtExt;
@@ -118,6 +118,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
118118

119119
self.assemble_closure_candidates(obligation, &mut candidates);
120120
self.assemble_fn_pointer_candidates(obligation, &mut candidates);
121+
self.assemble_candidates_from_exhaustive_impls(obligation, &mut candidates);
121122
self.assemble_candidates_from_impls(obligation, &mut candidates);
122123
self.assemble_candidates_from_object_ty(obligation, &mut candidates);
123124
}
@@ -348,6 +349,12 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
348349

349350
let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::ForLookup };
350351
let obligation_args = obligation.predicate.skip_binder().trait_ref.args;
352+
// disallow any adts to have recursive types in the LHS
353+
if let ty::Adt(_, args) = obligation.predicate.skip_binder().self_ty().kind() {
354+
if args.consts().any(|c| matches!(c.kind(), ConstKind::Expr(_))) {
355+
return;
356+
}
357+
}
351358
self.tcx().for_each_relevant_impl(
352359
obligation.predicate.def_id(),
353360
obligation.predicate.skip_binder().trait_ref.self_ty(),
@@ -465,6 +472,54 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
465472
}
466473
false
467474
}
475+
/// When constructing an impl over a generic const enum (i.e. bool = { true, false })
476+
/// If all possible variants of an enum are implemented AND the obligation is over that
477+
/// variant,
478+
fn assemble_candidates_from_exhaustive_impls(
479+
&mut self,
480+
obligation: &PolyTraitObligation<'tcx>,
481+
candidates: &mut SelectionCandidateSet<'tcx>,
482+
) {
483+
if !self.tcx().features().impl_exhaustive_const_traits {
484+
return;
485+
}
486+
487+
// see note in `assemble_candidates_from_impls`.
488+
if obligation.predicate.references_error() {
489+
return;
490+
}
491+
492+
// note: ow = otherwise
493+
// - check if trait has abstract const argument(s) which is (are) enum or bool, ow return
494+
// - check if trait enum is non-exhaustive, ow return
495+
// - construct required set of possible combinations, with false unless present
496+
// for each relevant trait
497+
// - check if is same trait
498+
// - set combo as present
499+
// If all required sets are present, add candidate impl generic over all combinations.
500+
501+
let query = obligation.predicate.skip_binder().self_ty();
502+
let ty::Adt(_adt_def, adt_substs) = query.kind() else {
503+
return;
504+
};
505+
506+
let Some(ct) = adt_substs
507+
.consts()
508+
.filter(|ct| {
509+
matches!(ct.kind(), ty::ConstKind::Unevaluated(..) | ty::ConstKind::Param(_))
510+
})
511+
.next()
512+
else {
513+
return;
514+
};
515+
516+
// explicitly gate certain types which are exhaustive
517+
if !super::exhaustive_types(self.tcx(), ct.ty(), |_| {}) {
518+
return;
519+
}
520+
521+
candidates.vec.push(ExhaustiveCandidate(obligation.predicate));
522+
}
468523

469524
fn assemble_candidates_from_auto_impls(
470525
&mut self,

compiler/rustc_trait_selection/src/traits/select/confirmation.rs

+76-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_infer::infer::{DefineOpaqueTypes, InferOk};
1414
use rustc_middle::traits::{BuiltinImplSource, SelectionOutputTypeParameterMismatch};
1515
use rustc_middle::ty::{
1616
self, GenericArgs, GenericArgsRef, GenericParamDefKind, ToPolyTraitRef, ToPredicate,
17-
TraitPredicate, Ty, TyCtxt, TypeVisitableExt,
17+
TraitPredicate, TraitRef, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeVisitableExt,
1818
};
1919
use rustc_span::def_id::DefId;
2020

@@ -120,6 +120,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
120120
let data = self.confirm_const_destruct_candidate(obligation, def_id)?;
121121
ImplSource::Builtin(BuiltinImplSource::Misc, data)
122122
}
123+
ExhaustiveCandidate(candidate) => {
124+
let obligations = self.confirm_exhaustive_candidate(obligation, candidate);
125+
ImplSource::Exhaustive(obligations)
126+
}
123127
};
124128

125129
// The obligations returned by confirmation are recursively evaluated
@@ -1360,4 +1364,75 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
13601364

13611365
Ok(nested)
13621366
}
1367+
1368+
/// Generates obligations for a lazy candidate
1369+
/// Where the obligations generated are all possible values for the type of a
1370+
/// `ConstKind::Unevaluated(..)`.
1371+
/// In the future, it would be nice to extend this to inductive proofs.
1372+
#[allow(unused_variables, unused_mut, dead_code)]
1373+
fn confirm_exhaustive_candidate(
1374+
&mut self,
1375+
obligation: &PolyTraitObligation<'tcx>,
1376+
candidate: ty::PolyTraitPredicate<'tcx>,
1377+
) -> Vec<PredicateObligation<'tcx>> {
1378+
let mut obligations = vec![];
1379+
let tcx = self.tcx();
1380+
1381+
let query = obligation.predicate.skip_binder().trait_ref.self_ty();
1382+
let ty::Adt(_adt_def, adt_substs) = query.kind() else {
1383+
return obligations;
1384+
};
1385+
// Find one adt subst at a time, then will be handled recursively.
1386+
let const_to_replace = adt_substs
1387+
.consts()
1388+
.find(|ct| {
1389+
matches!(ct.kind(), ty::ConstKind::Unevaluated(..) | ty::ConstKind::Param(_))
1390+
})
1391+
.unwrap();
1392+
let is_exhaustive = super::exhaustive_types(tcx, const_to_replace.ty(), |val| {
1393+
let predicate = candidate.map_bound(|pt_ref| {
1394+
let query = pt_ref.self_ty();
1395+
let mut folder = Folder { tcx, replace: const_to_replace, with: val };
1396+
let mut new_poly_trait_ref = pt_ref.clone();
1397+
new_poly_trait_ref.trait_ref = TraitRef::new(
1398+
tcx,
1399+
pt_ref.trait_ref.def_id,
1400+
[query.fold_with(&mut folder).into()]
1401+
.into_iter()
1402+
.chain(pt_ref.trait_ref.args.iter().skip(1)),
1403+
);
1404+
new_poly_trait_ref
1405+
});
1406+
1407+
let ob = Obligation::new(
1408+
self.tcx(),
1409+
obligation.cause.clone(),
1410+
obligation.param_env,
1411+
predicate,
1412+
);
1413+
obligations.push(ob);
1414+
});
1415+
1416+
// should only allow exhaustive types in
1417+
// candidate_assembly::assemble_candidates_from_exhaustive_impls
1418+
assert!(is_exhaustive);
1419+
1420+
obligations
1421+
}
1422+
}
1423+
1424+
/// Folder for replacing specific const values in `substs`.
1425+
struct Folder<'tcx> {
1426+
tcx: TyCtxt<'tcx>,
1427+
replace: ty::Const<'tcx>,
1428+
with: ty::Const<'tcx>,
1429+
}
1430+
1431+
impl<'tcx> TypeFolder<TyCtxt<'tcx>> for Folder<'tcx> {
1432+
fn interner(&self) -> TyCtxt<'tcx> {
1433+
self.tcx
1434+
}
1435+
fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> {
1436+
if c == self.replace { self.with } else { c }
1437+
}
13631438
}

compiler/rustc_trait_selection/src/traits/select/mod.rs

+52
Original file line numberDiff line numberDiff line change
@@ -1806,6 +1806,8 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
18061806
// This is a fix for #53123 and prevents winnowing from accidentally extending the
18071807
// lifetime of a variable.
18081808
match (&other.candidate, &victim.candidate) {
1809+
(_, ExhaustiveCandidate(..)) => DropVictim::Yes,
1810+
(ExhaustiveCandidate(..), _) => DropVictim::No,
18091811
// FIXME(@jswrenn): this should probably be more sophisticated
18101812
(TransmutabilityCandidate, _) | (_, TransmutabilityCandidate) => DropVictim::No,
18111813

@@ -2604,6 +2606,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
26042606
assert_eq!(predicates.parent, None);
26052607
let predicates = predicates.instantiate_own(tcx, args);
26062608
let mut obligations = Vec::with_capacity(predicates.len());
2609+
26072610
for (index, (predicate, span)) in predicates.into_iter().enumerate() {
26082611
let cause =
26092612
if Some(parent_trait_pred.def_id()) == tcx.lang_items().coerce_unsized_trait() {
@@ -2999,3 +3002,52 @@ fn bind_generator_hidden_types_above<'tcx>(
29993002
));
30003003
ty::Binder::bind_with_vars(hidden_types, bound_vars)
30013004
}
3005+
3006+
// For a given type, will run a function over all constants for that type if permitted.
3007+
// returns false if not permitted. Callers should not rely on the order.
3008+
fn exhaustive_types<'tcx>(
3009+
tcx: TyCtxt<'tcx>,
3010+
ty: Ty<'tcx>,
3011+
mut f: impl FnMut(ty::Const<'tcx>),
3012+
) -> bool {
3013+
use std::mem::transmute;
3014+
match ty.kind() {
3015+
ty::Bool => {
3016+
for v in [true, false].into_iter() {
3017+
f(ty::Const::from_bool(tcx, v));
3018+
}
3019+
}
3020+
// Should always compile, as this is never instantiable
3021+
ty::Never => {}
3022+
ty::Adt(adt_def, _substs) => {
3023+
if adt_def.is_payloadfree() {
3024+
return true;
3025+
}
3026+
if adt_def.is_variant_list_non_exhaustive() {
3027+
return false;
3028+
}
3029+
3030+
// FIXME(julianknodt): here need to create constants for each variant
3031+
return false;
3032+
}
3033+
3034+
ty::Int(ty::IntTy::I8) => {
3035+
for v in -128i8..127i8 {
3036+
let c = ty::Const::from_bits(
3037+
tcx,
3038+
unsafe { transmute(v as i128) },
3039+
ty::ParamEnv::empty().and(ty),
3040+
);
3041+
f(c);
3042+
}
3043+
}
3044+
ty::Uint(ty::UintTy::U8) => {
3045+
for v in 0u8..=255u8 {
3046+
let c = ty::Const::from_bits(tcx, v as u128, ty::ParamEnv::empty().and(ty));
3047+
f(c);
3048+
}
3049+
}
3050+
_ => return false,
3051+
}
3052+
true
3053+
}

compiler/rustc_ty_utils/src/instance.rs

+1
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ fn resolve_associated_item<'tcx>(
295295
}
296296
traits::ImplSource::Param(..)
297297
| traits::ImplSource::Builtin(BuiltinImplSource::TraitUpcasting { .. }, _)
298+
| traits::ImplSource::Exhaustive(..)
298299
| traits::ImplSource::Builtin(BuiltinImplSource::TupleUnsizing, _) => None,
299300
})
300301
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error[E0277]: the trait bound `ConstOption<usize, { N <= 0 }>: Default` is not satisfied
2+
--> $DIR/bool_cond.rs:42:5
3+
|
4+
LL | #[derive(Default)]
5+
| ------- in this derive macro expansion
6+
...
7+
LL | _a: ConstOption<usize, { N <= 0 }>,
8+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `ConstOption<usize, { N <= 0 }>`
9+
|
10+
= help: the following other types implement trait `Default`:
11+
ConstOption<T, false>
12+
ConstOption<T, true>
13+
= note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info)
14+
15+
error[E0277]: the trait bound `ConstOption<usize, { N <= 1 }>: Default` is not satisfied
16+
--> $DIR/bool_cond.rs:44:5
17+
|
18+
LL | #[derive(Default)]
19+
| ------- in this derive macro expansion
20+
...
21+
LL | _b: ConstOption<usize, { N <= 1 }>,
22+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `ConstOption<usize, { N <= 1 }>`
23+
|
24+
= help: the following other types implement trait `Default`:
25+
ConstOption<T, false>
26+
ConstOption<T, true>
27+
= note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info)
28+
29+
error: aborting due to 2 previous errors
30+
31+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)