Skip to content

Double-check conditional constness in MIR #132276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
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
103 changes: 72 additions & 31 deletions compiler/rustc_const_eval/src/check_consts/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use rustc_hir::def_id::DefId;
use rustc_hir::{self as hir, LangItem};
use rustc_index::bit_set::BitSet;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::ObligationCause;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
use rustc_middle::span_bug;
Expand All @@ -20,9 +19,11 @@ use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TypeVisitableExt};
use rustc_mir_dataflow::Analysis;
use rustc_mir_dataflow::impls::MaybeStorageLive;
use rustc_mir_dataflow::storage::always_storage_live_locals;
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
use rustc_span::{Span, Symbol, sym};
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
use rustc_trait_selection::traits::{self, ObligationCauseCode, ObligationCtxt};
use rustc_trait_selection::traits::{
Obligation, ObligationCause, ObligationCauseCode, ObligationCtxt,
};
use tracing::{debug, instrument, trace};

use super::ops::{self, NonConstOp, Status};
Expand Down Expand Up @@ -360,6 +361,73 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
// end of evaluation.
!is_transient
}

fn revalidate_conditional_constness(
&mut self,
callee: DefId,
callee_args: ty::GenericArgsRef<'tcx>,
call_source: CallSource,
call_span: Span,
) {
let tcx = self.tcx;
if !tcx.is_conditionally_const(callee) {
return;
}

let const_conditions = tcx.const_conditions(callee).instantiate(tcx, callee_args);
// If there are any const conditions on this fn and `const_trait_impl`
// is not enabled, simply bail. We shouldn't be able to call conditionally
// const functions on stable.
if !const_conditions.is_empty() && !tcx.features().const_trait_impl() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should never call tcx.features() in this code, as that won't properly check for rustc_allow_const_fn_unstable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the user still needs to enable the feature gate globally so that we can properly enforce the feature's validity earlier in the compiler.

self.check_op(ops::FnCallNonConst {
callee,
args: callee_args,
span: call_span,
call_source,
feature: Some(sym::const_trait_impl),
});
return;
}

let infcx = tcx.infer_ctxt().build(self.body.typing_mode(tcx));
let ocx = ObligationCtxt::new_with_diagnostics(&infcx);

let body_id = self.body.source.def_id().expect_local();
let host_polarity = match self.const_kind() {
hir::ConstContext::ConstFn => ty::BoundConstness::Maybe,
hir::ConstContext::Static(_) | hir::ConstContext::Const { .. } => {
ty::BoundConstness::Const
}
};
let const_conditions = ocx.normalize(
&ObligationCause::misc(call_span, body_id),
self.param_env,
const_conditions,
);
ocx.register_obligations(const_conditions.into_iter().map(|(trait_ref, span)| {
Obligation::new(
tcx,
ObligationCause::new(
call_span,
body_id,
ObligationCauseCode::WhereClause(callee, span),
),
self.param_env,
trait_ref.to_host_effect_clause(tcx, host_polarity),
)
}));

let errors = ocx.select_all_or_error();
if !errors.is_empty() {
// FIXME(effects): Soon this should be unconditionally delaying a bug.
if matches!(call_source, CallSource::Normal) && tcx.features().effects() {
tcx.dcx()
.span_delayed_bug(call_span, "this should have reported a ~const error in HIR");
} else {
infcx.err_ctxt().report_fulfillment_errors(errors);
}
}
}
}

impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
Expand Down Expand Up @@ -566,7 +634,6 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
};

let ConstCx { tcx, body, param_env, .. } = *self.ccx;
let caller = self.def_id();

let fn_ty = func.ty(body, tcx);

Expand All @@ -584,31 +651,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
}
};

// Check that all trait bounds that are marked as `~const` can be satisfied.
//
// Typeck only does a "non-const" check since it operates on HIR and cannot distinguish
// which path expressions are getting called on and which path expressions are only used
// as function pointers. This is required for correctness.
let infcx = tcx.infer_ctxt().build(body.typing_mode(tcx));
let ocx = ObligationCtxt::new_with_diagnostics(&infcx);

let predicates = tcx.predicates_of(callee).instantiate(tcx, fn_args);
let cause = ObligationCause::new(
terminator.source_info.span,
self.body.source.def_id().expect_local(),
ObligationCauseCode::WhereClause(callee, DUMMY_SP),
);
let normalized_predicates = ocx.normalize(&cause, param_env, predicates);
ocx.register_obligations(traits::predicates_for_generics(
|_, _| cause.clone(),
self.param_env,
normalized_predicates,
));

let errors = ocx.select_all_or_error();
if !errors.is_empty() {
infcx.err_ctxt().report_fulfillment_errors(errors);
}
self.revalidate_conditional_constness(callee, fn_args, call_source, *fn_span);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused, why is this called outside the if let Some(trait_did) = tcx.trait_of_item(callee) {? Can there be non-trait conditional const calls? Given that the const_trait_impl feature gate is used for these calls, I assume the answer is no...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes? We can put ~const bounds on free const functions.

Copy link
Member Author

@compiler-errors compiler-errors Nov 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the const_trait_impl feature gate is used for these calls

I think you're getting too stuck on the naming of the const_trait_impl feature gate.

The feature gate enables the declaration of const traits, impl const, and the use of ~const trait bounds on conditionally const items, which includes free const function. This is how the gate has been used for the last like... 3 years or whatever.

I don't want to rename the feature gate, since that basically is just churn. the feature gate could be called foo_bar for all it matters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a poorly chosen name. :(

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷 I'm sorry, but I didn't choose it, and I'm not really interested in doing a rename here. That can be done later, and ideally not when there are like 3 other PRs in flight concerning it.


let mut is_trait = false;
// Attempting to call a trait method?
Expand Down Expand Up @@ -648,7 +691,6 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
None
};
self.check_op(ops::FnCallNonConst {
caller,
callee,
args: fn_args,
span: *fn_span,
Expand Down Expand Up @@ -738,7 +780,6 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
// Trait functions are not `const fn` so we have to skip them here.
if !tcx.is_const_fn(callee) && !is_trait {
self.check_op(ops::FnCallNonConst {
caller,
callee,
args: fn_args,
span: *fn_span,
Expand Down
11 changes: 5 additions & 6 deletions compiler/rustc_const_eval/src/check_consts/ops.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Concrete error types for all operations which may be invalid in a certain const context.

use hir::def_id::LocalDefId;
use hir::{ConstContext, LangItem};
use rustc_errors::Diag;
use rustc_errors::codes::*;
Expand Down Expand Up @@ -74,7 +73,6 @@ impl<'tcx> NonConstOp<'tcx> for FnCallIndirect {
/// A function call where the callee is not marked as `const`.
#[derive(Debug, Clone, Copy)]
pub(crate) struct FnCallNonConst<'tcx> {
pub caller: LocalDefId,
pub callee: DefId,
pub args: GenericArgsRef<'tcx>,
pub span: Span,
Expand All @@ -87,8 +85,9 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
#[allow(rustc::diagnostic_outside_of_impl)]
#[allow(rustc::untranslatable_diagnostic)]
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, _: Span) -> Diag<'tcx> {
let FnCallNonConst { caller, callee, args, span, call_source, feature } = *self;
let ConstCx { tcx, param_env, body, .. } = *ccx;
let FnCallNonConst { callee, args, span, call_source, feature } = *self;
let ConstCx { tcx, param_env, .. } = *ccx;
let caller = ccx.def_id();

let diag_trait = |err, self_ty: Ty<'_>, trait_id| {
let trait_ref = TraitRef::from_method(tcx, trait_id, args);
Expand Down Expand Up @@ -116,7 +115,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
let obligation =
Obligation::new(tcx, ObligationCause::dummy(), param_env, trait_ref);

let infcx = tcx.infer_ctxt().build(body.typing_mode(tcx));
let infcx = tcx.infer_ctxt().build(ccx.body.typing_mode(tcx));
let mut selcx = SelectionContext::new(&infcx);
let implsrc = selcx.select(&obligation);

Expand Down Expand Up @@ -289,7 +288,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
if let Some(feature) = feature {
ccx.tcx.disabled_nightly_features(
&mut err,
body.source.def_id().as_local().map(|local| ccx.tcx.local_def_id_to_hir_id(local)),
Some(ccx.tcx.local_def_id_to_hir_id(caller)),
[(String::new(), feature)],
);
}
Expand Down
10 changes: 8 additions & 2 deletions tests/ui/traits/const-traits/const-drop-fail-2.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ LL | const fn check<T: ~const Destruct>(_: T) {}
| |
| the destructor for this type cannot be evaluated in constant functions

error[E0277]: the trait bound `T: ~const A` is not satisfied
--> $DIR/const-drop-fail-2.rs:41:9
|
LL | T::a();
| ^^^^^^

error[E0015]: cannot call non-const fn `<T as A>::a` in constant functions
--> $DIR/const-drop-fail-2.rs:41:9
|
Expand All @@ -41,7 +47,7 @@ help: add `#![feature(effects)]` to the crate attributes to enable
LL + #![feature(effects)]
|

error: aborting due to 5 previous errors
error: aborting due to 6 previous errors

Some errors have detailed explanations: E0015, E0493.
Some errors have detailed explanations: E0015, E0277, E0493.
For more information about an error, try `rustc --explain E0015`.
8 changes: 7 additions & 1 deletion tests/ui/traits/const-traits/const-drop.precise.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ error[E0493]: destructor of `T` cannot be evaluated at compile-time
LL | const fn a<T: ~const Destruct>(_: T) {}
| ^ the destructor for this type cannot be evaluated in constant functions

error[E0277]: the trait bound `T: ~const SomeTrait` is not satisfied
--> $DIR/const-drop.rs:69:13
|
LL | T::foo();
| ^^^^^^^^

error[E0015]: cannot call non-const fn `<T as SomeTrait>::foo` in constant functions
--> $DIR/const-drop.rs:69:13
|
Expand All @@ -90,7 +96,7 @@ help: add `#![feature(effects)]` to the crate attributes to enable
LL + #![feature(effects)]
|

error: aborting due to 10 previous errors
error: aborting due to 11 previous errors

Some errors have detailed explanations: E0015, E0277, E0493.
For more information about an error, try `rustc --explain E0015`.
8 changes: 7 additions & 1 deletion tests/ui/traits/const-traits/const-drop.stock.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ LL | const fn a<T: ~const Destruct>(_: T) {}
| |
| the destructor for this type cannot be evaluated in constant functions

error[E0277]: the trait bound `T: ~const SomeTrait` is not satisfied
--> $DIR/const-drop.rs:69:13
|
LL | T::foo();
| ^^^^^^^^

error[E0015]: cannot call non-const fn `<T as SomeTrait>::foo` in constant functions
--> $DIR/const-drop.rs:69:13
|
Expand All @@ -92,7 +98,7 @@ help: add `#![feature(effects)]` to the crate attributes to enable
LL + #![feature(effects)]
|

error: aborting due to 10 previous errors
error: aborting due to 11 previous errors

Some errors have detailed explanations: E0015, E0277, E0493.
For more information about an error, try `rustc --explain E0015`.
7 changes: 4 additions & 3 deletions tests/ui/traits/const-traits/cross-crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ const fn const_context() {
#[cfg(any(stocknc, gatednc))]
NonConst.func();
//[stocknc]~^ ERROR: cannot call
//[gatednc]~^^ ERROR: the trait bound
//[stocknc]~| ERROR: cannot call
//[gatednc]~^^^ ERROR: the trait bound
Const.func();
//[stock]~^ ERROR: cannot call
//[stocknc]~^^ ERROR: cannot call
//[stock,stocknc]~^ ERROR: cannot call
//[stock,stocknc]~| ERROR: cannot call
}

fn main() {}
17 changes: 15 additions & 2 deletions tests/ui/traits/const-traits/cross-crate.stock.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0015]: cannot call non-const fn `<cross_crate::Const as cross_crate::MyTrait>::func` in constant functions
--> $DIR/cross-crate.rs:22:11
--> $DIR/cross-crate.rs:23:11
|
LL | Const.func();
| ^^^^^^
Expand All @@ -10,6 +10,19 @@ help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
LL + #![feature(const_trait_impl)]
|

error: aborting due to 1 previous error
error[E0015]: cannot call non-const fn `<cross_crate::Const as cross_crate::MyTrait>::func` in constant functions
--> $DIR/cross-crate.rs:23:11
|
LL | Const.func();
| ^^^^^^
|
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
LL + #![feature(const_trait_impl)]
|

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0015`.
30 changes: 28 additions & 2 deletions tests/ui/traits/const-traits/cross-crate.stocknc.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,44 @@ help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
LL + #![feature(const_trait_impl)]
|

error[E0015]: cannot call non-const fn `<cross_crate::NonConst as cross_crate::MyTrait>::func` in constant functions
--> $DIR/cross-crate.rs:19:14
|
LL | NonConst.func();
| ^^^^^^
|
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
LL + #![feature(const_trait_impl)]
|

error[E0015]: cannot call non-const fn `<cross_crate::Const as cross_crate::MyTrait>::func` in constant functions
--> $DIR/cross-crate.rs:23:11
|
LL | Const.func();
| ^^^^^^
|
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
LL + #![feature(const_trait_impl)]
|

error[E0015]: cannot call non-const fn `<cross_crate::Const as cross_crate::MyTrait>::func` in constant functions
--> $DIR/cross-crate.rs:22:11
--> $DIR/cross-crate.rs:23:11
|
LL | Const.func();
| ^^^^^^
|
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
|
LL + #![feature(const_trait_impl)]
|

error: aborting due to 2 previous errors
error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0015`.
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
//@ known-bug: #110395
//@ check-pass

#![feature(const_trait_impl)]

#[const_trait]
Expand All @@ -13,7 +10,7 @@ const fn foo<T>() where T: ~const Tr {}
pub trait Foo {
fn foo() {
foo::<()>();
//FIXME ~^ ERROR the trait bound `(): Tr` is not satisfied
//~^ ERROR the trait bound `(): ~const Tr` is not satisfied
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error[E0277]: the trait bound `(): ~const Tr` is not satisfied
--> $DIR/default-method-body-is-const-body-checking.rs:12:9
|
LL | foo::<()>();
| ^^^^^^^^^^^
|
note: required by a bound in `foo`
--> $DIR/default-method-body-is-const-body-checking.rs:7:28
|
LL | const fn foo<T>() where T: ~const Tr {}
| ^^^^^^ required by this bound in `foo`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.
Loading
Loading