diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index f41d70d384d8..79e7eb0f1cc2 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -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; + } } } } diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs index 7e15858c8c18..329d1033abf5 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs @@ -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 = >::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!( diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs b/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs index a7b4d08cbf3e..31eb2b654764 100644 --- a/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs +++ b/compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs @@ -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( diff --git a/src/test/ui/rfc-2632-const-trait-impl/static-const-trait-bound.rs b/src/test/ui/rfc-2632-const-trait-impl/static-const-trait-bound.rs new file mode 100644 index 000000000000..4520a36960c7 --- /dev/null +++ b/src/test/ui/rfc-2632-const-trait-impl/static-const-trait-bound.rs @@ -0,0 +1,18 @@ +// check-pass +pub struct S T = fn() -> T> { + f: F, + x: Option, +} + +impl T> S { + pub const fn new(f: F) -> Self { + Self { f, x: None } + } +} + +#[derive(Default)] +pub struct Foo; + +static LOCKED_CALLSITES: S = S::new(Default::default); + +fn main() {} diff --git a/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause-const.rs b/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause-const.rs new file mode 100644 index 000000000000..7a88ec35c8f2 --- /dev/null +++ b/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause-const.rs @@ -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(); +} + +const fn test1() { + T::a(); + T::b(); + //~^ ERROR the trait bound + T::c::(); + //~^ ERROR the trait bound +} + +const fn test2() { + T::a(); + T::b(); + T::c::(); +} + +fn main() {} diff --git a/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause-const.stderr b/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause-const.stderr new file mode 100644 index 000000000000..13d8639de30c --- /dev/null +++ b/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause-const.stderr @@ -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() { + | ++++++++++++ + +error[E0277]: the trait bound `T: ~const Bar` is not satisfied + --> $DIR/trait-where-clause-const.rs:19:5 + | +LL | T::c::(); + | ^^^^^^^^^^^ 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::(); + | ^^^^^^^^^^^ +help: consider further restricting this bound + | +LL | const fn test1() { + | ++++++++++++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause.rs b/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause.rs index d37ed3bb8dd9..5bd23a8cb207 100644 --- a/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause.rs +++ b/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause.rs @@ -8,7 +8,7 @@ trait Foo { fn c(); } -const fn test1() { +fn test1() { T::a(); T::b(); //~^ ERROR the trait bound @@ -16,21 +16,7 @@ const fn test1() { //~^ ERROR the trait bound } -const fn test2() { - T::a(); - T::b(); - T::c::(); -} - -fn test3() { - T::a(); - T::b(); - //~^ ERROR the trait bound - T::c::(); - //~^ ERROR the trait bound -} - -fn test4() { +fn test2() { T::a(); T::b(); T::c::(); diff --git a/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause.stderr b/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause.stderr index fd5fe25ddcfb..96365d334336 100644 --- a/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause.stderr +++ b/src/test/ui/rfc-2632-const-trait-impl/trait-where-clause.stderr @@ -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() { - | ++++++++++++ - -error[E0277]: the trait bound `T: ~const Bar` is not satisfied - --> $DIR/trait-where-clause.rs:15:12 - | -LL | T::c::(); - | ^ 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::(); - | ^ -note: required by a bound in `Foo::c` - --> $DIR/trait-where-clause.rs:8:13 - | -LL | fn c(); - | ^^^^^^^^^^ required by this bound in `Foo::c` -help: consider further restricting this bound - | -LL | const fn test1() { - | ++++++++++++ - 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() { +LL | fn test1() { | +++++ 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::(); | ^ the trait `Bar` is not implemented for `T` @@ -69,9 +27,9 @@ LL | fn c(); | ^^^^^^^^^^ required by this bound in `Foo::c` help: consider further restricting this bound | -LL | fn test3() { +LL | fn test1() { | +++++ -error: aborting due to 4 previous errors +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0277`.