do const trait method bounds check later in rustc_const_eval

This commit is contained in:
Deadbeef 2022-09-10 06:37:26 +00:00
parent 1bcc26a6b1
commit f8813cf10e
8 changed files with 174 additions and 74 deletions

View File

@ -424,12 +424,31 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'tcx> {
}
if let ty::FnDef(def_id, substs) = *constant.literal.ty().kind() {
// N.B.: When instantiating a trait method as a function item, it does not actually matter
// whether the trait is `const` or not, or whether `where T: ~const Tr` needs to be satisfied
// as `const`. If we were to introduce instantiating trait methods as `const fn`s, we would
// check that after this, either via a bound `where F: ~const FnOnce` or when coercing to a
// `const fn` pointer.
//
// FIXME(fee1-dead) FIXME(const_trait_impl): update this doc when trait methods can satisfy
// `~const FnOnce` or can be coerced to `const fn` pointer.
let const_norm = self.tcx().def_kind(def_id) == hir::def::DefKind::AssocFn
&& self.tcx().def_kind(ty::DefIdTree::parent(self.tcx(), def_id))
== hir::def::DefKind::Trait;
let instantiated_predicates = tcx.predicates_of(def_id).instantiate(tcx, substs);
let prev = self.cx.param_env;
if const_norm {
self.cx.param_env = prev.without_const();
}
self.cx.normalize_and_prove_instantiated_predicates(
def_id,
instantiated_predicates,
locations,
);
if const_norm {
self.cx.param_env = prev;
}
}
}
}

View File

@ -13,8 +13,11 @@ use rustc_middle::ty::{self, adjustment::PointerCast, Instance, InstanceDef, Ty,
use rustc_middle::ty::{Binder, TraitPredicate, TraitRef, TypeVisitable};
use rustc_mir_dataflow::{self, Analysis};
use rustc_span::{sym, Span, Symbol};
use rustc_trait_selection::traits::error_reporting::InferCtxtExt;
use rustc_trait_selection::traits::SelectionContext;
use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
use rustc_trait_selection::traits::{
self, ObligationCauseCode, SelectionContext, TraitEngine, TraitEngineExt,
};
use std::mem;
use std::ops::Deref;
@ -738,6 +741,43 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
selcx.select(&obligation)
});
// do a well-formedness check on the trait method being called. This is because typeck only does a
// "non-const" check. This is required for correctness here.
tcx.infer_ctxt().enter(|infcx| {
let mut fulfill_cx = <dyn TraitEngine<'_>>::new(infcx.tcx);
let predicates = tcx.predicates_of(callee).instantiate(tcx, substs);
let hir_id = tcx
.hir()
.local_def_id_to_hir_id(self.body.source.def_id().expect_local());
let cause = || {
ObligationCause::new(
terminator.source_info.span,
hir_id,
ObligationCauseCode::ItemObligation(callee),
)
};
let normalized = infcx.partially_normalize_associated_types_in(
cause(),
param_env,
predicates,
);
for p in normalized.obligations {
fulfill_cx.register_predicate_obligation(&infcx, p);
}
for obligation in traits::predicates_for_generics(
|_, _| cause(),
self.param_env,
normalized.value,
) {
fulfill_cx.register_predicate_obligation(&infcx, obligation);
}
let errors = fulfill_cx.select_all_or_error(&infcx);
if !errors.is_empty() {
infcx.report_fulfillment_errors(&errors, None, false);
}
});
match implsrc {
Ok(Some(ImplSource::Param(_, ty::BoundConstness::ConstIfConst))) => {
debug!(

View File

@ -1410,7 +1410,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
})
}
#[instrument(level = "debug", skip(self, code, span, def_id, substs))]
#[instrument(level = "debug", skip(self, code, span, substs))]
fn add_required_obligations_with_code(
&self,
span: Span,
@ -1418,14 +1418,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
substs: SubstsRef<'tcx>,
code: impl Fn(usize, Span) -> ObligationCauseCode<'tcx>,
) {
// Associated consts have `Self: ~const Trait` bounds that should be satisfiable when
// `Self: Trait` is satisfied because it does not matter whether the impl is `const`.
// Therefore we have to remap the param env here to be non-const.
let param_env = if let hir::def::DefKind::AssocConst = self.tcx.def_kind(def_id) {
self.param_env.without_const()
} else {
self.param_env
};
let mut param_env = self.param_env;
match self.tcx.def_kind(def_id) {
// Associated consts have `Self: ~const Trait` bounds that should be satisfiable when
// `Self: Trait` is satisfied because it does not matter whether the impl is `const`.
// Therefore we have to remap the param env here to be non-const.
hir::def::DefKind::AssocConst => param_env = param_env.without_const(),
hir::def::DefKind::AssocFn
if self.tcx.def_kind(self.tcx.parent(def_id)) == hir::def::DefKind::Trait =>
{
// N.B.: All callsites to this function involve checking a path expression.
//
// When instantiating a trait method as a function item, it does not actually matter whether
// the trait is `const` or not, or whether `where T: ~const Tr` needs to be satisfied as
// `const`. If we were to introduce instantiating trait methods as `const fn`s, we would
// check that after this, either via a bound `where F: ~const FnOnce` or when coercing to a
// `const fn` pointer.
//
// FIXME(fee1-dead) FIXME(const_trait_impl): update this doc when trait methods can satisfy
// `~const FnOnce` or can be coerced to `const fn` pointer.
param_env = param_env.without_const();
}
_ => {}
}
let (bounds, _) = self.instantiate_bounds(span, def_id, &substs);
for obligation in traits::predicates_for_generics(

View File

@ -0,0 +1,18 @@
// check-pass
pub struct S<T, F: FnOnce() -> T = fn() -> T> {
f: F,
x: Option<T>,
}
impl<T, F: FnOnce() -> T> S<T, F> {
pub const fn new(f: F) -> Self {
Self { f, x: None }
}
}
#[derive(Default)]
pub struct Foo;
static LOCKED_CALLSITES: S<Foo> = S::new(Default::default);
fn main() {}

View File

@ -0,0 +1,29 @@
// Like trait-where-clause.rs, but we are calling from a const context.
// Checking the validity of traits' where clauses happen at a later stage.
// (`rustc_const_eval` instead of `rustc_typeck`) Therefore one file as a
// test is not enough.
#![feature(const_trait_impl)]
trait Bar {}
trait Foo {
fn a();
fn b() where Self: ~const Bar;
fn c<T: ~const Bar>();
}
const fn test1<T: ~const Foo + Bar>() {
T::a();
T::b();
//~^ ERROR the trait bound
T::c::<T>();
//~^ ERROR the trait bound
}
const fn test2<T: ~const Foo + ~const Bar>() {
T::a();
T::b();
T::c::<T>();
}
fn main() {}

View File

@ -0,0 +1,35 @@
error[E0277]: the trait bound `T: ~const Bar` is not satisfied
--> $DIR/trait-where-clause-const.rs:17:5
|
LL | T::b();
| ^^^^^^ the trait `~const Bar` is not implemented for `T`
|
note: the trait `Bar` is implemented for `T`, but that implementation is not `const`
--> $DIR/trait-where-clause-const.rs:17:5
|
LL | T::b();
| ^^^^^^
help: consider further restricting this bound
|
LL | const fn test1<T: ~const Foo + Bar + ~const Bar>() {
| ++++++++++++
error[E0277]: the trait bound `T: ~const Bar` is not satisfied
--> $DIR/trait-where-clause-const.rs:19:5
|
LL | T::c::<T>();
| ^^^^^^^^^^^ the trait `~const Bar` is not implemented for `T`
|
note: the trait `Bar` is implemented for `T`, but that implementation is not `const`
--> $DIR/trait-where-clause-const.rs:19:5
|
LL | T::c::<T>();
| ^^^^^^^^^^^
help: consider further restricting this bound
|
LL | const fn test1<T: ~const Foo + Bar + ~const Bar>() {
| ++++++++++++
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0277`.

View File

@ -8,7 +8,7 @@ trait Foo {
fn c<T: ~const Bar>();
}
const fn test1<T: ~const Foo + Bar>() {
fn test1<T: Foo>() {
T::a();
T::b();
//~^ ERROR the trait bound
@ -16,21 +16,7 @@ const fn test1<T: ~const Foo + Bar>() {
//~^ ERROR the trait bound
}
const fn test2<T: ~const Foo + ~const Bar>() {
T::a();
T::b();
T::c::<T>();
}
fn test3<T: Foo>() {
T::a();
T::b();
//~^ ERROR the trait bound
T::c::<T>();
//~^ ERROR the trait bound
}
fn test4<T: Foo + Bar>() {
fn test2<T: Foo + Bar>() {
T::a();
T::b();
T::c::<T>();

View File

@ -1,47 +1,5 @@
error[E0277]: the trait bound `T: ~const Bar` is not satisfied
--> $DIR/trait-where-clause.rs:13:5
|
LL | T::b();
| ^^^^ the trait `~const Bar` is not implemented for `T`
|
note: the trait `Bar` is implemented for `T`, but that implementation is not `const`
--> $DIR/trait-where-clause.rs:13:5
|
LL | T::b();
| ^^^^
note: required by a bound in `Foo::b`
--> $DIR/trait-where-clause.rs:7:24
|
LL | fn b() where Self: ~const Bar;
| ^^^^^^^^^^ required by this bound in `Foo::b`
help: consider further restricting this bound
|
LL | const fn test1<T: ~const Foo + Bar + ~const Bar>() {
| ++++++++++++
error[E0277]: the trait bound `T: ~const Bar` is not satisfied
--> $DIR/trait-where-clause.rs:15:12
|
LL | T::c::<T>();
| ^ the trait `~const Bar` is not implemented for `T`
|
note: the trait `Bar` is implemented for `T`, but that implementation is not `const`
--> $DIR/trait-where-clause.rs:15:12
|
LL | T::c::<T>();
| ^
note: required by a bound in `Foo::c`
--> $DIR/trait-where-clause.rs:8:13
|
LL | fn c<T: ~const Bar>();
| ^^^^^^^^^^ required by this bound in `Foo::c`
help: consider further restricting this bound
|
LL | const fn test1<T: ~const Foo + Bar + ~const Bar>() {
| ++++++++++++
error[E0277]: the trait bound `T: Bar` is not satisfied
--> $DIR/trait-where-clause.rs:27:5
--> $DIR/trait-where-clause.rs:13:5
|
LL | T::b();
| ^^^^ the trait `Bar` is not implemented for `T`
@ -53,11 +11,11 @@ LL | fn b() where Self: ~const Bar;
| ^^^^^^^^^^ required by this bound in `Foo::b`
help: consider further restricting this bound
|
LL | fn test3<T: Foo + Bar>() {
LL | fn test1<T: Foo + Bar>() {
| +++++
error[E0277]: the trait bound `T: Bar` is not satisfied
--> $DIR/trait-where-clause.rs:29:12
--> $DIR/trait-where-clause.rs:15:12
|
LL | T::c::<T>();
| ^ the trait `Bar` is not implemented for `T`
@ -69,9 +27,9 @@ LL | fn c<T: ~const Bar>();
| ^^^^^^^^^^ required by this bound in `Foo::c`
help: consider further restricting this bound
|
LL | fn test3<T: Foo + Bar>() {
LL | fn test1<T: Foo + Bar>() {
| +++++
error: aborting due to 4 previous errors
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0277`.