Auto merge of #77354 - ecstatic-morse:const-checking-moar-errors, r=oli-obk
Overhaul const-checking diagnostics The primary purpose of this PR was to remove `NonConstOp::STOPS_CONST_CHECKING`, which causes any additional errors found by the const-checker to be silenced. I used this flag to preserve diagnostic parity with `qualify_min_const_fn.rs`, which has since been removed. However, simply removing the flag caused a deluge of errors in some cases, since an error would be emitted any time a local or temporary had a wrong type. To remedy this, I added an alternative system (`DiagnosticImportance`) to silence additional error messages that were likely to distract the user from the underlying issue. When an error of the highest importance occurs, all less important errors are silenced. When no error of the highest importance occurs, all less important errors are emitted after checking is complete. Following the suggestions from the important error is usually enough to fix the less important errors, so this should lead to better UX most of the time. There's also some unrelated diagnostics improvements in this PR isolated in their own commits. Splitting them out would be possible, but a bit of a pain. This isn't as tidy as some of my other PRs, but it should *only* affect diagnostics, never whether or not something passes const-checking. Note that there are a few trivial exceptions to this, like banning `Yield` in all const-contexts, not just `const fn`. As always, meant to be reviewed commit-by-commit. r? `@oli-obk`
This commit is contained in:
commit
fc42fb8e70
58 changed files with 594 additions and 607 deletions
|
@ -57,6 +57,16 @@ impl ConstCx<'mir, 'tcx> {
|
|||
&& self.tcx.features().staged_api
|
||||
&& is_const_stable_const_fn(self.tcx, self.def_id.to_def_id())
|
||||
}
|
||||
|
||||
/// Returns the function signature of the item being const-checked if it is a `fn` or `const fn`.
|
||||
pub fn fn_sig(&self) -> Option<&'tcx hir::FnSig<'tcx>> {
|
||||
// Get this from the HIR map instead of a query to avoid cycle errors.
|
||||
//
|
||||
// FIXME: Is this still an issue?
|
||||
let hir_map = self.tcx.hir();
|
||||
let hir_id = hir_map.local_def_id_to_hir_id(self.def_id);
|
||||
hir_map.fn_sig_by_hir_id(hir_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this `DefId` points to one of the official `panic` lang items.
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
//! Concrete error types for all operations which may be invalid in a certain const context.
|
||||
|
||||
use rustc_errors::{struct_span_err, Applicability};
|
||||
use rustc_errors::{struct_span_err, DiagnosticBuilder};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_middle::mir;
|
||||
use rustc_session::config::nightly_options;
|
||||
use rustc_session::parse::feature_err;
|
||||
use rustc_span::symbol::sym;
|
||||
|
@ -10,49 +11,6 @@ use rustc_span::{Span, Symbol};
|
|||
|
||||
use super::ConstCx;
|
||||
|
||||
/// Emits an error and returns `true` if `op` is not allowed in the given const context.
|
||||
pub fn non_const<O: NonConstOp>(ccx: &ConstCx<'_, '_>, op: O, span: Span) -> bool {
|
||||
debug!("illegal_op: op={:?}", op);
|
||||
|
||||
let gate = match op.status_in_item(ccx) {
|
||||
Status::Allowed => return false,
|
||||
|
||||
Status::Unstable(gate) if ccx.tcx.features().enabled(gate) => {
|
||||
let unstable_in_stable = ccx.is_const_stable_const_fn()
|
||||
&& !super::allow_internal_unstable(ccx.tcx, ccx.def_id.to_def_id(), gate);
|
||||
|
||||
if unstable_in_stable {
|
||||
ccx.tcx.sess
|
||||
.struct_span_err(
|
||||
span,
|
||||
&format!("const-stable function cannot use `#[feature({})]`", gate.as_str()),
|
||||
)
|
||||
.span_suggestion(
|
||||
ccx.body.span,
|
||||
"if it is not part of the public API, make this function unstably const",
|
||||
concat!(r#"#[rustc_const_unstable(feature = "...", issue = "...")]"#, '\n').to_owned(),
|
||||
Applicability::HasPlaceholders,
|
||||
)
|
||||
.note("otherwise `#[allow_internal_unstable]` can be used to bypass stability checks")
|
||||
.emit();
|
||||
}
|
||||
|
||||
return unstable_in_stable;
|
||||
}
|
||||
|
||||
Status::Unstable(gate) => Some(gate),
|
||||
Status::Forbidden => None,
|
||||
};
|
||||
|
||||
if ccx.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
|
||||
ccx.tcx.sess.miri_unleashed_feature(span, gate);
|
||||
return false;
|
||||
}
|
||||
|
||||
op.emit_error(ccx, span);
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
Allowed,
|
||||
|
@ -60,63 +18,44 @@ pub enum Status {
|
|||
Forbidden,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum DiagnosticImportance {
|
||||
/// An operation that must be removed for const-checking to pass.
|
||||
Primary,
|
||||
|
||||
/// An operation that causes const-checking to fail, but is usually a side-effect of a `Primary` operation elsewhere.
|
||||
Secondary,
|
||||
}
|
||||
|
||||
/// An operation that is not *always* allowed in a const context.
|
||||
pub trait NonConstOp: std::fmt::Debug {
|
||||
const STOPS_CONST_CHECKING: bool = false;
|
||||
|
||||
/// Returns an enum indicating whether this operation is allowed within the given item.
|
||||
fn status_in_item(&self, _ccx: &ConstCx<'_, '_>) -> Status {
|
||||
Status::Forbidden
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
let mut err = struct_span_err!(
|
||||
ccx.tcx.sess,
|
||||
span,
|
||||
E0019,
|
||||
"{} contains unimplemented expression type",
|
||||
ccx.const_kind()
|
||||
);
|
||||
|
||||
if let Status::Unstable(gate) = self.status_in_item(ccx) {
|
||||
if !ccx.tcx.features().enabled(gate) && nightly_options::is_nightly_build() {
|
||||
err.help(&format!("add `#![feature({})]` to the crate attributes to enable", gate));
|
||||
}
|
||||
}
|
||||
|
||||
if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
|
||||
err.note(
|
||||
"A function call isn't allowed in the const's initialization expression \
|
||||
because the expression's value must be known at compile-time.",
|
||||
);
|
||||
err.note(
|
||||
"Remember: you can't use a function call inside a const's initialization \
|
||||
expression! However, you can use it anywhere else.",
|
||||
);
|
||||
}
|
||||
err.emit();
|
||||
fn importance(&self) -> DiagnosticImportance {
|
||||
DiagnosticImportance::Primary
|
||||
}
|
||||
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Abort;
|
||||
impl NonConstOp for Abort {
|
||||
const STOPS_CONST_CHECKING: bool = true;
|
||||
|
||||
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
|
||||
mcf_status_in_item(ccx)
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
mcf_emit_error(ccx, span, "abort is not stable in const fn")
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
mcf_build_error(ccx, span, "abort is not stable in const fn")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FloatingPointOp;
|
||||
impl NonConstOp for FloatingPointOp {
|
||||
const STOPS_CONST_CHECKING: bool = true;
|
||||
|
||||
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
|
||||
if ccx.const_kind() == hir::ConstContext::ConstFn {
|
||||
Status::Unstable(sym::const_fn_floating_point_arithmetic)
|
||||
|
@ -125,28 +64,13 @@ impl NonConstOp for FloatingPointOp {
|
|||
}
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
sym::const_fn_floating_point_arithmetic,
|
||||
span,
|
||||
&format!("floating point arithmetic is not allowed in {}s", ccx.const_kind()),
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NonPrimitiveOp;
|
||||
impl NonConstOp for NonPrimitiveOp {
|
||||
const STOPS_CONST_CHECKING: bool = true;
|
||||
|
||||
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
|
||||
mcf_status_in_item(ccx)
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
mcf_emit_error(ccx, span, "only int, `bool` and `char` operations are stable in const fn")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,10 +78,8 @@ impl NonConstOp for NonPrimitiveOp {
|
|||
#[derive(Debug)]
|
||||
pub struct FnCallIndirect;
|
||||
impl NonConstOp for FnCallIndirect {
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
let mut err =
|
||||
ccx.tcx.sess.struct_span_err(span, "function pointers are not allowed in const fn");
|
||||
err.emit();
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
ccx.tcx.sess.struct_span_err(span, "function pointers are not allowed in const fn")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,16 +87,15 @@ impl NonConstOp for FnCallIndirect {
|
|||
#[derive(Debug)]
|
||||
pub struct FnCallNonConst(pub DefId);
|
||||
impl NonConstOp for FnCallNonConst {
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
let mut err = struct_span_err!(
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
struct_span_err!(
|
||||
ccx.tcx.sess,
|
||||
span,
|
||||
E0015,
|
||||
"calls in {}s are limited to constant functions, \
|
||||
tuple structs and tuple variants",
|
||||
ccx.const_kind(),
|
||||
);
|
||||
err.emit();
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +106,7 @@ impl NonConstOp for FnCallNonConst {
|
|||
pub struct FnCallUnstable(pub DefId, pub Option<Symbol>);
|
||||
|
||||
impl NonConstOp for FnCallUnstable {
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
let FnCallUnstable(def_id, feature) = *self;
|
||||
|
||||
let mut err = ccx.tcx.sess.struct_span_err(
|
||||
|
@ -203,15 +124,14 @@ impl NonConstOp for FnCallUnstable {
|
|||
));
|
||||
}
|
||||
}
|
||||
err.emit();
|
||||
|
||||
err
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FnPtrCast;
|
||||
impl NonConstOp for FnPtrCast {
|
||||
const STOPS_CONST_CHECKING: bool = true;
|
||||
|
||||
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
|
||||
if ccx.const_kind() != hir::ConstContext::ConstFn {
|
||||
Status::Allowed
|
||||
|
@ -220,37 +140,32 @@ impl NonConstOp for FnPtrCast {
|
|||
}
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
sym::const_fn_fn_ptr_basics,
|
||||
span,
|
||||
&format!("function pointer casts are not allowed in {}s", ccx.const_kind()),
|
||||
)
|
||||
.emit()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Generator;
|
||||
impl NonConstOp for Generator {
|
||||
const STOPS_CONST_CHECKING: bool = true;
|
||||
|
||||
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
|
||||
// FIXME: This means generator-only MIR is only forbidden in const fn. This is for
|
||||
// compatibility with the old code. Such MIR should be forbidden everywhere.
|
||||
mcf_status_in_item(ccx)
|
||||
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
|
||||
Status::Forbidden
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
mcf_emit_error(ccx, span, "const fn generators are unstable");
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
ccx.tcx.sess.struct_span_err(span, "Generators and `async` functions cannot be `const`")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HeapAllocation;
|
||||
impl NonConstOp for HeapAllocation {
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
let mut err = struct_span_err!(
|
||||
ccx.tcx.sess,
|
||||
span,
|
||||
|
@ -267,38 +182,48 @@ impl NonConstOp for HeapAllocation {
|
|||
be done at compile time.",
|
||||
);
|
||||
}
|
||||
err.emit();
|
||||
err
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InlineAsm;
|
||||
impl NonConstOp for InlineAsm {}
|
||||
impl NonConstOp for InlineAsm {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
struct_span_err!(
|
||||
ccx.tcx.sess,
|
||||
span,
|
||||
E0015,
|
||||
"inline assembly is not allowed in {}s",
|
||||
ccx.const_kind()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LiveDrop {
|
||||
pub dropped_at: Option<Span>,
|
||||
}
|
||||
impl NonConstOp for LiveDrop {
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
let mut diagnostic = struct_span_err!(
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
let mut err = struct_span_err!(
|
||||
ccx.tcx.sess,
|
||||
span,
|
||||
E0493,
|
||||
"destructors cannot be evaluated at compile-time"
|
||||
);
|
||||
diagnostic.span_label(span, format!("{}s cannot evaluate destructors", ccx.const_kind()));
|
||||
err.span_label(span, format!("{}s cannot evaluate destructors", ccx.const_kind()));
|
||||
if let Some(span) = self.dropped_at {
|
||||
diagnostic.span_label(span, "value is dropped here");
|
||||
err.span_label(span, "value is dropped here");
|
||||
}
|
||||
diagnostic.emit();
|
||||
err
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CellBorrow;
|
||||
impl NonConstOp for CellBorrow {
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
struct_span_err!(
|
||||
ccx.tcx.sess,
|
||||
span,
|
||||
|
@ -306,7 +231,6 @@ impl NonConstOp for CellBorrow {
|
|||
"cannot borrow a constant which may contain \
|
||||
interior mutability, create a static instead"
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -322,7 +246,7 @@ impl NonConstOp for MutBorrow {
|
|||
}
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
let mut err = if ccx.const_kind() == hir::ConstContext::ConstFn {
|
||||
feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
|
@ -353,7 +277,7 @@ impl NonConstOp for MutBorrow {
|
|||
static mut or a global UnsafeCell.",
|
||||
);
|
||||
}
|
||||
err.emit();
|
||||
err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,14 +294,13 @@ impl NonConstOp for MutAddressOf {
|
|||
}
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
sym::const_mut_refs,
|
||||
span,
|
||||
&format!("`&raw mut` is not allowed in {}s", ccx.const_kind()),
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -387,6 +310,20 @@ impl NonConstOp for MutDeref {
|
|||
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
|
||||
Status::Unstable(sym::const_mut_refs)
|
||||
}
|
||||
|
||||
fn importance(&self) -> DiagnosticImportance {
|
||||
// Usually a side-effect of a `MutBorrow` somewhere.
|
||||
DiagnosticImportance::Secondary
|
||||
}
|
||||
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
sym::const_mut_refs,
|
||||
span,
|
||||
&format!("mutation through a reference is not allowed in {}s", ccx.const_kind()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -396,21 +333,20 @@ impl NonConstOp for Panic {
|
|||
Status::Unstable(sym::const_panic)
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
sym::const_panic,
|
||||
span,
|
||||
&format!("panicking in {}s is unstable", ccx.const_kind()),
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RawPtrComparison;
|
||||
impl NonConstOp for RawPtrComparison {
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
let mut err = ccx
|
||||
.tcx
|
||||
.sess
|
||||
|
@ -419,7 +355,7 @@ impl NonConstOp for RawPtrComparison {
|
|||
"see issue #53020 <https://github.com/rust-lang/rust/issues/53020> \
|
||||
for more information",
|
||||
);
|
||||
err.emit();
|
||||
err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -430,14 +366,13 @@ impl NonConstOp for RawPtrDeref {
|
|||
Status::Unstable(sym::const_raw_ptr_deref)
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
sym::const_raw_ptr_deref,
|
||||
span,
|
||||
&format!("dereferencing raw pointers in {}s is unstable", ccx.const_kind(),),
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -448,14 +383,13 @@ impl NonConstOp for RawPtrToIntCast {
|
|||
Status::Unstable(sym::const_raw_ptr_to_usize_cast)
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
sym::const_raw_ptr_to_usize_cast,
|
||||
span,
|
||||
&format!("casting pointers to integers in {}s is unstable", ccx.const_kind(),),
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -471,7 +405,7 @@ impl NonConstOp for StaticAccess {
|
|||
}
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
let mut err = struct_span_err!(
|
||||
ccx.tcx.sess,
|
||||
span,
|
||||
|
@ -489,7 +423,7 @@ impl NonConstOp for StaticAccess {
|
|||
);
|
||||
err.help("To fix this, the value can be extracted to a `const` and then used.");
|
||||
}
|
||||
err.emit();
|
||||
err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -497,7 +431,7 @@ impl NonConstOp for StaticAccess {
|
|||
#[derive(Debug)]
|
||||
pub struct ThreadLocalAccess;
|
||||
impl NonConstOp for ThreadLocalAccess {
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
struct_span_err!(
|
||||
ccx.tcx.sess,
|
||||
span,
|
||||
|
@ -505,15 +439,12 @@ impl NonConstOp for ThreadLocalAccess {
|
|||
"thread-local statics cannot be \
|
||||
accessed at compile-time"
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Transmute;
|
||||
impl NonConstOp for Transmute {
|
||||
const STOPS_CONST_CHECKING: bool = true;
|
||||
|
||||
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
|
||||
if ccx.const_kind() != hir::ConstContext::ConstFn {
|
||||
Status::Allowed
|
||||
|
@ -522,15 +453,15 @@ impl NonConstOp for Transmute {
|
|||
}
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
feature_err(
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
let mut err = feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
sym::const_fn_transmute,
|
||||
span,
|
||||
&format!("`transmute` is not allowed in {}s", ccx.const_kind()),
|
||||
)
|
||||
.note("`transmute` is only allowed in constants and statics for now")
|
||||
.emit();
|
||||
);
|
||||
err.note("`transmute` is only allowed in constants and statics for now");
|
||||
err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -546,14 +477,13 @@ impl NonConstOp for UnionAccess {
|
|||
}
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
sym::const_fn_union,
|
||||
span,
|
||||
"unions in const fn are unstable",
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -567,12 +497,12 @@ impl NonConstOp for UnsizingCast {
|
|||
mcf_status_in_item(ccx)
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
mcf_emit_error(
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
mcf_build_error(
|
||||
ccx,
|
||||
span,
|
||||
"unsizing casts to types besides slices are not allowed in const fn",
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -581,29 +511,42 @@ pub mod ty {
|
|||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MutRef;
|
||||
pub struct MutRef(pub mir::LocalKind);
|
||||
impl NonConstOp for MutRef {
|
||||
const STOPS_CONST_CHECKING: bool = true;
|
||||
|
||||
fn status_in_item(&self, _ccx: &ConstCx<'_, '_>) -> Status {
|
||||
Status::Unstable(sym::const_mut_refs)
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn importance(&self) -> DiagnosticImportance {
|
||||
match self.0 {
|
||||
mir::LocalKind::Var | mir::LocalKind::Temp => DiagnosticImportance::Secondary,
|
||||
mir::LocalKind::ReturnPointer | mir::LocalKind::Arg => {
|
||||
DiagnosticImportance::Primary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
sym::const_mut_refs,
|
||||
span,
|
||||
&format!("mutable references are not allowed in {}s", ccx.const_kind()),
|
||||
)
|
||||
.emit()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FnPtr;
|
||||
pub struct FnPtr(pub mir::LocalKind);
|
||||
impl NonConstOp for FnPtr {
|
||||
const STOPS_CONST_CHECKING: bool = true;
|
||||
fn importance(&self) -> DiagnosticImportance {
|
||||
match self.0 {
|
||||
mir::LocalKind::Var | mir::LocalKind::Temp => DiagnosticImportance::Secondary,
|
||||
mir::LocalKind::ReturnPointer | mir::LocalKind::Arg => {
|
||||
DiagnosticImportance::Primary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
|
||||
if ccx.const_kind() != hir::ConstContext::ConstFn {
|
||||
|
@ -613,46 +556,50 @@ pub mod ty {
|
|||
}
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
sym::const_fn_fn_ptr_basics,
|
||||
span,
|
||||
&format!("function pointers cannot appear in {}s", ccx.const_kind()),
|
||||
)
|
||||
.emit()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ImplTrait;
|
||||
impl NonConstOp for ImplTrait {
|
||||
const STOPS_CONST_CHECKING: bool = true;
|
||||
|
||||
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
|
||||
mcf_status_in_item(ccx)
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
mcf_emit_error(ccx, span, "`impl Trait` in const fn is unstable");
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
mcf_build_error(ccx, span, "`impl Trait` in const fn is unstable")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TraitBound;
|
||||
pub struct TraitBound(pub mir::LocalKind);
|
||||
impl NonConstOp for TraitBound {
|
||||
const STOPS_CONST_CHECKING: bool = true;
|
||||
fn importance(&self) -> DiagnosticImportance {
|
||||
match self.0 {
|
||||
mir::LocalKind::Var | mir::LocalKind::Temp => DiagnosticImportance::Secondary,
|
||||
mir::LocalKind::ReturnPointer | mir::LocalKind::Arg => {
|
||||
DiagnosticImportance::Primary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
|
||||
mcf_status_in_item(ccx)
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
mcf_emit_error(
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
mcf_build_error(
|
||||
ccx,
|
||||
span,
|
||||
"trait bounds other than `Sized` on const fn parameters are unstable",
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -660,20 +607,17 @@ pub mod ty {
|
|||
#[derive(Debug)]
|
||||
pub struct TraitBoundNotConst;
|
||||
impl NonConstOp for TraitBoundNotConst {
|
||||
const STOPS_CONST_CHECKING: bool = true;
|
||||
|
||||
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
|
||||
Status::Unstable(sym::const_trait_bound_opt_out)
|
||||
}
|
||||
|
||||
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
|
||||
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
|
||||
feature_err(
|
||||
&ccx.tcx.sess.parse_sess,
|
||||
sym::const_trait_bound_opt_out,
|
||||
span,
|
||||
"`?const Trait` syntax is unstable",
|
||||
)
|
||||
.emit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -686,12 +630,12 @@ fn mcf_status_in_item(ccx: &ConstCx<'_, '_>) -> Status {
|
|||
}
|
||||
}
|
||||
|
||||
fn mcf_emit_error(ccx: &ConstCx<'_, '_>, span: Span, msg: &str) {
|
||||
struct_span_err!(ccx.tcx.sess, span, E0723, "{}", msg)
|
||||
.note(
|
||||
"see issue #57563 <https://github.com/rust-lang/rust/issues/57563> \
|
||||
fn mcf_build_error(ccx: &ConstCx<'_, 'tcx>, span: Span, msg: &str) -> DiagnosticBuilder<'tcx> {
|
||||
let mut err = struct_span_err!(ccx.tcx.sess, span, E0723, "{}", msg);
|
||||
err.note(
|
||||
"see issue #57563 <https://github.com/rust-lang/rust/issues/57563> \
|
||||
for more information",
|
||||
)
|
||||
.help("add `#![feature(const_fn)]` to the crate attributes to enable")
|
||||
.emit();
|
||||
);
|
||||
err.help("add `#![feature(const_fn)]` to the crate attributes to enable");
|
||||
err
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use rustc_middle::mir::{self, BasicBlock, Location};
|
|||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_span::Span;
|
||||
|
||||
use super::ops;
|
||||
use super::ops::{self, NonConstOp};
|
||||
use super::qualifs::{NeedsDrop, Qualif};
|
||||
use super::validation::Qualifs;
|
||||
use super::ConstCx;
|
||||
|
@ -56,7 +56,7 @@ impl std::ops::Deref for CheckLiveDrops<'mir, 'tcx> {
|
|||
|
||||
impl CheckLiveDrops<'mir, 'tcx> {
|
||||
fn check_live_drop(&self, span: Span) {
|
||||
ops::non_const(self.ccx, ops::LiveDrop { dropped_at: None }, span);
|
||||
ops::LiveDrop { dropped_at: None }.build_error(self.ccx, span).emit();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//! The `Visitor` responsible for actually checking a `mir::Body` for invalid operations.
|
||||
|
||||
use rustc_errors::struct_span_err;
|
||||
use rustc_hir::{self as hir, LangItem};
|
||||
use rustc_hir::{def_id::DefId, HirId};
|
||||
use rustc_errors::{struct_span_err, Applicability, Diagnostic};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{self as hir, HirId, LangItem};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
|
||||
use rustc_middle::mir::*;
|
||||
|
@ -11,13 +11,14 @@ use rustc_middle::ty::subst::GenericArgKind;
|
|||
use rustc_middle::ty::{
|
||||
self, adjustment::PointerCast, Instance, InstanceDef, Ty, TyCtxt, TypeAndMut,
|
||||
};
|
||||
use rustc_span::{sym, Span};
|
||||
use rustc_span::{sym, Span, Symbol};
|
||||
use rustc_trait_selection::traits::error_reporting::InferCtxtExt;
|
||||
use rustc_trait_selection::traits::{self, TraitEngine};
|
||||
|
||||
use std::mem;
|
||||
use std::ops::Deref;
|
||||
|
||||
use super::ops::{self, NonConstOp};
|
||||
use super::ops::{self, NonConstOp, Status};
|
||||
use super::qualifs::{self, CustomEq, HasMutInterior, NeedsDrop};
|
||||
use super::resolver::FlowSensitiveAnalysis;
|
||||
use super::{is_lang_panic_fn, ConstCx, Qualif};
|
||||
|
@ -180,7 +181,8 @@ pub struct Validator<'mir, 'tcx> {
|
|||
/// The span of the current statement.
|
||||
span: Span,
|
||||
|
||||
const_checking_stopped: bool,
|
||||
error_emitted: bool,
|
||||
secondary_errors: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
impl Deref for Validator<'mir, 'tcx> {
|
||||
|
@ -197,13 +199,21 @@ impl Validator<'mir, 'tcx> {
|
|||
span: ccx.body.span,
|
||||
ccx,
|
||||
qualifs: Default::default(),
|
||||
const_checking_stopped: false,
|
||||
error_emitted: false,
|
||||
secondary_errors: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_body(&mut self) {
|
||||
let ConstCx { tcx, body, def_id, .. } = *self.ccx;
|
||||
|
||||
// `async` functions cannot be `const fn`. This is checked during AST lowering, so there's
|
||||
// no need to emit duplicate errors here.
|
||||
if is_async_fn(self.ccx) || body.generator_kind.is_some() {
|
||||
tcx.sess.delay_span_bug(body.span, "`async` functions cannot be `const fn`");
|
||||
return;
|
||||
}
|
||||
|
||||
// The local type and predicate checks are not free and only relevant for `const fn`s.
|
||||
if self.const_kind() == hir::ConstContext::ConstFn {
|
||||
// Prevent const trait methods from being annotated as `stable`.
|
||||
|
@ -223,20 +233,21 @@ impl Validator<'mir, 'tcx> {
|
|||
|
||||
self.check_item_predicates();
|
||||
|
||||
for local in &body.local_decls {
|
||||
if local.internal {
|
||||
for (idx, local) in body.local_decls.iter_enumerated() {
|
||||
// Handle the return place below.
|
||||
if idx == RETURN_PLACE || local.internal {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.span = local.source_info.span;
|
||||
self.check_local_or_return_ty(local.ty);
|
||||
self.check_local_or_return_ty(local.ty, idx);
|
||||
}
|
||||
|
||||
// impl trait is gone in MIR, so check the return type of a const fn by its signature
|
||||
// instead of the type of the return place.
|
||||
self.span = body.local_decls[RETURN_PLACE].source_info.span;
|
||||
let return_ty = tcx.fn_sig(def_id).output();
|
||||
self.check_local_or_return_ty(return_ty.skip_binder());
|
||||
self.check_local_or_return_ty(return_ty.skip_binder(), RETURN_PLACE);
|
||||
}
|
||||
|
||||
self.visit_body(&body);
|
||||
|
@ -250,6 +261,17 @@ impl Validator<'mir, 'tcx> {
|
|||
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
|
||||
check_return_ty_is_sync(tcx, &body, hir_id);
|
||||
}
|
||||
|
||||
// If we got through const-checking without emitting any "primary" errors, emit any
|
||||
// "secondary" errors if they occurred.
|
||||
let secondary_errors = mem::take(&mut self.secondary_errors);
|
||||
if !self.error_emitted {
|
||||
for error in secondary_errors {
|
||||
self.tcx.sess.diagnostic().emit_diagnostic(&error);
|
||||
}
|
||||
} else {
|
||||
assert!(self.tcx.sess.has_errors());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn qualifs_in_return_place(&mut self) -> ConstQualifs {
|
||||
|
@ -264,15 +286,38 @@ impl Validator<'mir, 'tcx> {
|
|||
/// Emits an error at the given `span` if an expression cannot be evaluated in the current
|
||||
/// context.
|
||||
pub fn check_op_spanned<O: NonConstOp>(&mut self, op: O, span: Span) {
|
||||
// HACK: This is for strict equivalence with the old `qualify_min_const_fn` pass, which
|
||||
// only emitted one error per function. It should be removed and the test output updated.
|
||||
if self.const_checking_stopped {
|
||||
let gate = match op.status_in_item(self.ccx) {
|
||||
Status::Allowed => return,
|
||||
|
||||
Status::Unstable(gate) if self.tcx.features().enabled(gate) => {
|
||||
let unstable_in_stable = self.ccx.is_const_stable_const_fn()
|
||||
&& !super::allow_internal_unstable(self.tcx, self.def_id.to_def_id(), gate);
|
||||
if unstable_in_stable {
|
||||
emit_unstable_in_stable_error(self.ccx, span, gate);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Status::Unstable(gate) => Some(gate),
|
||||
Status::Forbidden => None,
|
||||
};
|
||||
|
||||
if self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
|
||||
self.tcx.sess.miri_unleashed_feature(span, gate);
|
||||
return;
|
||||
}
|
||||
|
||||
let err_emitted = ops::non_const(self.ccx, op, span);
|
||||
if err_emitted && O::STOPS_CONST_CHECKING {
|
||||
self.const_checking_stopped = true;
|
||||
let mut err = op.build_error(self.ccx, span);
|
||||
assert!(err.is_error());
|
||||
|
||||
match op.importance() {
|
||||
ops::DiagnosticImportance::Primary => {
|
||||
self.error_emitted = true;
|
||||
err.emit();
|
||||
}
|
||||
|
||||
ops::DiagnosticImportance::Secondary => err.buffer(&mut self.secondary_errors),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -284,7 +329,9 @@ impl Validator<'mir, 'tcx> {
|
|||
self.check_op_spanned(ops::StaticAccess, span)
|
||||
}
|
||||
|
||||
fn check_local_or_return_ty(&mut self, ty: Ty<'tcx>) {
|
||||
fn check_local_or_return_ty(&mut self, ty: Ty<'tcx>, local: Local) {
|
||||
let kind = self.body.local_kind(local);
|
||||
|
||||
for ty in ty.walk() {
|
||||
let ty = match ty.unpack() {
|
||||
GenericArgKind::Type(ty) => ty,
|
||||
|
@ -295,20 +342,20 @@ impl Validator<'mir, 'tcx> {
|
|||
};
|
||||
|
||||
match *ty.kind() {
|
||||
ty::Ref(_, _, hir::Mutability::Mut) => self.check_op(ops::ty::MutRef),
|
||||
ty::Ref(_, _, hir::Mutability::Mut) => self.check_op(ops::ty::MutRef(kind)),
|
||||
ty::Opaque(..) => self.check_op(ops::ty::ImplTrait),
|
||||
ty::FnPtr(..) => self.check_op(ops::ty::FnPtr),
|
||||
ty::FnPtr(..) => self.check_op(ops::ty::FnPtr(kind)),
|
||||
|
||||
ty::Dynamic(preds, _) => {
|
||||
for pred in preds.iter() {
|
||||
match pred.skip_binder() {
|
||||
ty::ExistentialPredicate::AutoTrait(_)
|
||||
| ty::ExistentialPredicate::Projection(_) => {
|
||||
self.check_op(ops::ty::TraitBound)
|
||||
self.check_op(ops::ty::TraitBound(kind))
|
||||
}
|
||||
ty::ExistentialPredicate::Trait(trait_ref) => {
|
||||
if Some(trait_ref.def_id) != self.tcx.lang_items().sized_trait() {
|
||||
self.check_op(ops::ty::TraitBound)
|
||||
self.check_op(ops::ty::TraitBound(kind))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -353,15 +400,19 @@ impl Validator<'mir, 'tcx> {
|
|||
let def = generics.type_param(p, tcx);
|
||||
let span = tcx.def_span(def.def_id);
|
||||
|
||||
// These are part of the function signature, so treat them like
|
||||
// arguments when determining importance.
|
||||
let kind = LocalKind::Arg;
|
||||
|
||||
if constness == hir::Constness::Const {
|
||||
self.check_op_spanned(ops::ty::TraitBound, span);
|
||||
self.check_op_spanned(ops::ty::TraitBound(kind), span);
|
||||
} else if !tcx.features().const_fn
|
||||
|| self.ccx.is_const_stable_const_fn()
|
||||
{
|
||||
// HACK: We shouldn't need the conditional above, but trait
|
||||
// bounds on containing impl blocks are wrongly being marked as
|
||||
// "not-const".
|
||||
self.check_op_spanned(ops::ty::TraitBound, span);
|
||||
self.check_op_spanned(ops::ty::TraitBound(kind), span);
|
||||
}
|
||||
}
|
||||
// other kinds of bounds are either tautologies
|
||||
|
@ -877,3 +928,31 @@ fn place_as_reborrow(
|
|||
fn is_int_bool_or_char(ty: Ty<'_>) -> bool {
|
||||
ty.is_bool() || ty.is_integral() || ty.is_char()
|
||||
}
|
||||
|
||||
fn is_async_fn(ccx: &ConstCx<'_, '_>) -> bool {
|
||||
ccx.fn_sig().map_or(false, |sig| sig.header.asyncness == hir::IsAsync::Async)
|
||||
}
|
||||
|
||||
fn emit_unstable_in_stable_error(ccx: &ConstCx<'_, '_>, span: Span, gate: Symbol) {
|
||||
let attr_span = ccx.fn_sig().map_or(ccx.body.span, |sig| sig.span.shrink_to_lo());
|
||||
|
||||
ccx.tcx
|
||||
.sess
|
||||
.struct_span_err(
|
||||
span,
|
||||
&format!("const-stable function cannot use `#[feature({})]`", gate.as_str()),
|
||||
)
|
||||
.span_suggestion(
|
||||
attr_span,
|
||||
"if it is not part of the public API, make this function unstably const",
|
||||
concat!(r#"#[rustc_const_unstable(feature = "...", issue = "...")]"#, '\n').to_owned(),
|
||||
Applicability::HasPlaceholders,
|
||||
)
|
||||
.span_suggestion(
|
||||
attr_span,
|
||||
"otherwise `#[allow_internal_unstable]` can be used to bypass stability checks",
|
||||
format!("#[allow_internal_unstable({})]\n", gate),
|
||||
Applicability::MaybeIncorrect,
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue