Skip to content
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

Add speculative const-checking and remove qualify_min_const_fn.rs #77128

Closed
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
1 change: 1 addition & 0 deletions compiler/rustc_mir/src/transform/check_consts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use rustc_middle::ty::{self, TyCtxt};
use rustc_span::Symbol;

pub use self::qualifs::Qualif;
pub use self::validation::non_const_fn_could_be_made_stable_const_fn;

mod ops;
pub mod post_drop_elaboration;
Expand Down
45 changes: 1 addition & 44 deletions compiler/rustc_mir/src/transform/check_consts/ops.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! 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;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_session::config::nightly_options;
Expand All @@ -10,49 +10,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 }.emit_error(self.ccx, span)
}
}

Expand Down
7 changes: 5 additions & 2 deletions compiler/rustc_mir/src/transform/check_consts/qualifs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,11 @@ where
};

// Check the qualifs of the value of `const` items.
if let ty::ConstKind::Unevaluated(def, _, promoted) = constant.literal.val {
assert!(promoted.is_none());
if let ty::ConstKind::Unevaluated(def, _, _promoted) = constant.literal.val {
// FIXME(rust-lang/rust-clippy#6080): Const-checking should never see promoteds, but clippy
// is broken.
// assert!(promoted.is_none());

// Don't peek inside trait associated constants.
if cx.tcx.trait_of_item(def.did).is_none() {
let qualifs = if let Some((did, param_did)) = def.as_const_arg() {
Expand Down
138 changes: 113 additions & 25 deletions compiler/rustc_mir/src/transform/check_consts/validation.rs
Original file line number Diff line number Diff line change
@@ -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};
use rustc_hir::def_id::{DefId, LocalDefId};
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::*;
Expand All @@ -11,20 +11,48 @@ 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::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};
use crate::const_eval::is_unstable_const_fn;
use crate::dataflow::impls::MaybeMutBorrowedLocals;
use crate::dataflow::{self, Analysis};

/// Returns `true` if the given `fn` could be made into a `const fn` without depending on any
/// unstable features.
///
/// This is used by clippy. Do not use it for const-checking.
pub fn non_const_fn_could_be_made_stable_const_fn(
tcx: TyCtxt<'tcx>,
def_id: LocalDefId,
body: &Body<'tcx>,
) -> bool {
let const_kind = tcx.hir().body_const_context(def_id);

// Only run this on non-const `fn`s.
assert!(const_kind.is_none());

let ccx = ConstCx {
body,
tcx,
def_id,
const_kind: Some(hir::ConstContext::ConstFn),
param_env: tcx.param_env(def_id),
};

let mut checker = Validator::new(&ccx);
checker.silence_errors = true;
checker.check_body();
checker.passes_checks_without_unstable_features
}

// We are using `MaybeMutBorrowedLocals` as a proxy for whether an item may have been mutated
// through a pointer prior to the given point. This is okay even though `MaybeMutBorrowedLocals`
// kills locals upon `StorageDead` because a local will never be used after a `StorageDead`.
Expand Down Expand Up @@ -179,7 +207,12 @@ pub struct Validator<'mir, 'tcx> {
/// The span of the current statement.
span: Span,

const_checking_stopped: bool,
/// True if we shouldn't emit errors when we find them.
///
/// This allows items to be speculatively const-checked.
silence_errors: bool,

passes_checks_without_unstable_features: bool,
}

impl Deref for Validator<'mir, 'tcx> {
Expand All @@ -196,7 +229,8 @@ impl Validator<'mir, 'tcx> {
span: ccx.body.span,
ccx,
qualifs: Default::default(),
const_checking_stopped: false,
passes_checks_without_unstable_features: true,
silence_errors: false,
}
}

Expand Down Expand Up @@ -266,15 +300,48 @@ 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 {
debug!("illegal_op: op={:?}", op);

let ccx = self.ccx;

let gate = match op.status_in_item(ccx) {
Status::Allowed => return,
Status::Unstable(gate) => Some(gate),
Status::Forbidden => None,
};

self.passes_checks_without_unstable_features = false;

if self.silence_errors {
return;
}

// Unless we are const-checking a const-stable function, return before emitting an error if
// the user has enabled the requisite feature gate.
if let Some(gate) = 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 {
error_unstable_in_stable(ccx, gate, span);
}

return;
}
}

if ccx.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
ccx.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;
op.emit_error(ccx, 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 O::STOPS_CONST_CHECKING {
self.silence_errors = true;
}
}

Expand Down Expand Up @@ -774,12 +841,6 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> {
// projections that cannot be `NeedsDrop`.
TerminatorKind::Drop { place: dropped_place, .. }
| TerminatorKind::DropAndReplace { place: dropped_place, .. } => {
// If we are checking live drops after drop-elaboration, don't emit duplicate
// errors here.
if super::post_drop_elaboration::checking_enabled(self.ccx) {
return;
}

let mut err_span = self.span;

// Check to see if the type of this place can ever have a drop impl. If not, this
Expand All @@ -791,20 +852,30 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> {
return;
}

let needs_drop = if let Some(local) = dropped_place.as_local() {
let local_needs_drop = if let Some(local) = dropped_place.as_local() {
// Use the span where the local was declared as the span of the drop error.
err_span = self.body.local_decls[local].source_info.span;
self.qualifs.needs_drop(self.ccx, local, location)
} else {
true
};

if needs_drop {
self.check_op_spanned(
ops::LiveDrop { dropped_at: Some(terminator.source_info.span) },
err_span,
);
if !local_needs_drop {
return;
}

self.passes_checks_without_unstable_features = false;

// If we are checking live drops after drop-elaboration, don't emit duplicate
// errors here.
if super::post_drop_elaboration::checking_enabled(self.ccx) {
return;
}

self.check_op_spanned(
ops::LiveDrop { dropped_at: Some(terminator.source_info.span) },
err_span,
);
}

TerminatorKind::InlineAsm { .. } => self.check_op(ops::InlineAsm),
Expand Down Expand Up @@ -866,3 +937,20 @@ fn place_as_reborrow(
}
})
}

fn error_unstable_in_stable(ccx: &ConstCx<'_, '_>, gate: Symbol, span: Span) {
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();
}
1 change: 0 additions & 1 deletion compiler/rustc_mir/src/transform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ pub mod match_branches;
pub mod no_landing_pads;
pub mod nrvo;
pub mod promote_consts;
pub mod qualify_min_const_fn;
pub mod remove_noop_landing_pads;
pub mod required_consts;
pub mod rustc_peek;
Expand Down
Loading