Skip to content

Commit

Permalink
Allow trait method paths to satisfy const Fn bounds
Browse files Browse the repository at this point in the history
  • Loading branch information
fee1-dead committed Dec 28, 2022
1 parent 6a4624d commit 983606d
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 13 deletions.
26 changes: 19 additions & 7 deletions compiler/rustc_trait_selection/src/traits/select/confirmation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use rustc_index::bit_set::GrowableBitSet;
use rustc_infer::infer::InferOk;
use rustc_infer::infer::LateBoundRegionConversionTime::HigherRankedType;
use rustc_middle::ty::{
self, GenericArg, GenericArgKind, GenericParamDefKind, InternalSubsts, SubstsRef,
ToPolyTraitRef, ToPredicate, Ty, TyCtxt,
self, Binder, GenericArg, GenericArgKind, GenericParamDefKind, InternalSubsts, SubstsRef,
ToPolyTraitRef, ToPredicate, TraitRef, Ty, TyCtxt,
};
use rustc_span::def_id::DefId;

Expand Down Expand Up @@ -98,8 +98,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
ImplSource::Future(vtable_future)
}

FnPointerCandidate { .. } => {
let data = self.confirm_fn_pointer_candidate(obligation)?;
FnPointerCandidate { is_const } => {
let data = self.confirm_fn_pointer_candidate(obligation, is_const)?;
ImplSource::FnPointer(data)
}

Expand Down Expand Up @@ -597,17 +597,19 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
fn confirm_fn_pointer_candidate(
&mut self,
obligation: &TraitObligation<'tcx>,
is_const: bool,
) -> Result<ImplSourceFnPointerData<'tcx, PredicateObligation<'tcx>>, SelectionError<'tcx>>
{
debug!(?obligation, "confirm_fn_pointer_candidate");

let tcx = self.tcx();
let self_ty = self
.infcx
.shallow_resolve(obligation.self_ty().no_bound_vars())
.expect("fn pointer should not capture bound vars from predicate");
let sig = self_ty.fn_sig(self.tcx());
let sig = self_ty.fn_sig(tcx);
let trait_ref = closure_trait_ref_and_return_type(
self.tcx(),
tcx,
obligation.predicate.def_id(),
self_ty,
sig,
Expand All @@ -616,9 +618,19 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
.map_bound(|(trait_ref, _)| trait_ref);

let mut nested = self.confirm_poly_trait_refs(obligation, trait_ref)?;
let cause = obligation.derived_cause(BuiltinDerivedObligation);

if obligation.is_const() && !is_const {
// function is a trait method
if let ty::FnDef(def_id, substs) = self_ty.kind() && let Some(trait_id) = tcx.trait_of_item(*def_id) {
let trait_ref = TraitRef::from_method(tcx, trait_id, *substs);
let poly_trait_pred = Binder::dummy(trait_ref).with_constness(ty::BoundConstness::ConstIfConst);
let obligation = Obligation::new(tcx, cause.clone(), obligation.param_env, poly_trait_pred);
nested.push(obligation);
}
}

// Confirm the `type Output: Sized;` bound that is present on `FnOnce`
let cause = obligation.derived_cause(BuiltinDerivedObligation);
let output_ty = self.infcx.replace_bound_vars_with_placeholders(sig.output());
let output_ty = normalize_with_depth_to(
self,
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_trait_selection/src/traits/select/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1374,6 +1374,14 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
FutureCandidate => {}
// FnDef where the function is const
FnPointerCandidate { is_const: true } => {}
FnPointerCandidate { is_const: false } => {
if let ty::FnDef(def_id, _) = obligation.self_ty().skip_binder().kind() && tcx.trait_of_item(*def_id).is_some() {
// Trait methods are not seen as const unless the trait is implemented as const.
// We do not filter that out in here, but nested obligations will be needed to confirm this.
} else {
continue
}
}
ConstDestructCandidate(_) => {}
_ => {
// reject all other types of candidates
Expand Down
18 changes: 12 additions & 6 deletions library/core/src/cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -800,9 +800,12 @@ pub trait Ord: Eq + PartialOrd<Self> {
Self: Sized,
Self: ~const Destruct,
{
// HACK(fee1-dead): go back to using `self.max_by(other, Ord::cmp)`
// when trait methods are allowed to be used when a const closure is
// expected.
#[cfg(not(bootstrap))]
{
max_by(self, other, Ord::cmp)
}

#[cfg(bootstrap)]
match self.cmp(&other) {
Ordering::Less | Ordering::Equal => other,
Ordering::Greater => self,
Expand All @@ -827,9 +830,12 @@ pub trait Ord: Eq + PartialOrd<Self> {
Self: Sized,
Self: ~const Destruct,
{
// HACK(fee1-dead): go back to using `self.min_by(other, Ord::cmp)`
// when trait methods are allowed to be used when a const closure is
// expected.
#[cfg(not(bootstrap))]
{
min_by(self, other, Ord::cmp)
}

#[cfg(bootstrap)]
match self.cmp(&other) {
Ordering::Less | Ordering::Equal => self,
Ordering::Greater => other,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![feature(const_trait_impl)]

#[const_trait]
trait Tr {
fn a(self) -> i32;
}

impl Tr for () {
fn a(self) -> i32 { 42 }
}

const fn need_const_closure<T: ~const FnOnce(()) -> i32>(x: T) -> i32 {
x(())
}

const _: () = assert!(need_const_closure(Tr::a) == 42);
//~^ ERROR: the trait bound

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
error[E0277]: the trait bound `(): ~const Tr` is not satisfied in `fn(()) -> i32 {<() as Tr>::a}`
--> $DIR/const-closure-trait-method-fail.rs:16:42
|
LL | const _: () = assert!(need_const_closure(Tr::a) == 42);
| ------------------ ^^^^^ within `fn(()) -> i32 {<() as Tr>::a}`, the trait `~const Tr` is not implemented for `()`
| |
| required by a bound introduced by this call
|
note: the trait `Tr` is implemented for `()`, but that implementation is not `const`
--> $DIR/const-closure-trait-method-fail.rs:16:42
|
LL | const _: () = assert!(need_const_closure(Tr::a) == 42);
| ^^^^^
= note: required because it appears within the type `fn(()) -> i32 {<() as Tr>::a}`
note: required by a bound in `need_const_closure`
--> $DIR/const-closure-trait-method-fail.rs:12:32
|
LL | const fn need_const_closure<T: ~const FnOnce(()) -> i32>(x: T) -> i32 {
| ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `need_const_closure`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// check-pass
#![feature(const_trait_impl)]

#[const_trait]
trait Tr {
fn a(self) -> i32;
}

impl const Tr for () {
fn a(self) -> i32 { 42 }
}

const fn need_const_closure<T: ~const FnOnce(()) -> i32>(x: T) -> i32 {
x(())
}

const _: () = assert!(need_const_closure(Tr::a) == 42);

fn main() {}

0 comments on commit 983606d

Please sign in to comment.