Skip to content

Commit

Permalink
Rollup merge of rust-lang#76850 - ecstatic-morse:const-checking-refac…
Browse files Browse the repository at this point in the history
…tor, r=oli-obk

Remove `qualify_min_const_fn`

~~Blocked on rust-lang#76807 (the first six commits).~~

With this PR, all checks in `qualify_min_const_fn` are replicated in `check_consts`, and the former is no longer invoked. My goal was to have as few changes to test output as possible, since making sweeping changes to the code *while* doing big batches of diagnostics updates turned out to be a headache. To this end, there's a few `HACK`s in `check_consts` to achieve parity with `qualify_min_const_fn`.

The new system that replaces `is_min_const_fn` is referred to as "const-stability"  My end goal for the const-stability rules is this:
* Const-stability is only applicable to functions defined in `staged_api` crates.
* All functions not marked `rustc_const_unstable` are considered "const-stable".
    - NB. This is currently not implemented. `#[unstable]` functions are also const-unstable. This causes problems when searching for feature gates.
    - All "const-unstable" functions have an associated feature gate
* const-stable functions can only call other const-stable functions
     - `allow_internal_unstable` can be used to circumvent this.
* All const-stable functions are subject to some additional checks (the ones that were unique to `qualify_min_const_fn`)

The plan is to remove each `HACK` individually in subsequent PRs. That way, changes to error message output can be reviewed in isolation.
  • Loading branch information
ecstatic-morse authored Sep 22, 2020
2 parents 1871d90 + 186d148 commit 00569c3
Show file tree
Hide file tree
Showing 54 changed files with 791 additions and 259 deletions.
1 change: 1 addition & 0 deletions compiler/rustc_ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/", test(attr(deny(warnings))))]
#![feature(box_syntax)]
#![feature(const_fn)] // For the `transmute` in `P::new`
#![feature(const_fn_transmute)]
#![feature(const_panic)]
#![feature(crate_visibility_modifier)]
#![feature(label_break_value)]
Expand Down
40 changes: 40 additions & 0 deletions compiler/rustc_mir/src/transform/check_consts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ impl ConstCx<'mir, 'tcx> {
pub fn const_kind(&self) -> hir::ConstContext {
self.const_kind.expect("`const_kind` must not be called on a non-const fn")
}

pub fn is_const_stable_const_fn(&self) -> bool {
self.const_kind == Some(hir::ConstContext::ConstFn)
&& self.tcx.features().staged_api
&& is_const_stable_const_fn(self.tcx, self.def_id.to_def_id())
}
}

/// Returns `true` if this `DefId` points to one of the official `panic` lang items.
Expand All @@ -63,3 +69,37 @@ pub fn allow_internal_unstable(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: S
attr::allow_internal_unstable(&tcx.sess, attrs)
.map_or(false, |mut features| features.any(|name| name == feature_gate))
}

// Returns `true` if the given `const fn` is "const-stable".
//
// Panics if the given `DefId` does not refer to a `const fn`.
//
// Const-stability is only relevant for `const fn` within a `staged_api` crate. Only "const-stable"
// functions can be called in a const-context by users of the stable compiler. "const-stable"
// functions are subject to more stringent restrictions than "const-unstable" functions: They
// cannot use unstable features and can only call other "const-stable" functions.
pub fn is_const_stable_const_fn(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool {
use attr::{ConstStability, Stability, StabilityLevel};

// Const-stability is only relevant for `const fn`.
assert!(tcx.is_const_fn_raw(def_id));

// Functions with `#[rustc_const_unstable]` are const-unstable.
match tcx.lookup_const_stability(def_id) {
Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. }) => return false,
Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }) => return true,
None => {}
}

// Functions with `#[unstable]` are const-unstable.
//
// FIXME(ecstaticmorse): We should keep const-stability attributes wholly separate from normal stability
// attributes. `#[unstable]` should be irrelevant.
if let Some(Stability { level: StabilityLevel::Unstable { .. }, .. }) =
tcx.lookup_stability(def_id)
{
return false;
}

true
}
244 changes: 231 additions & 13 deletions compiler/rustc_mir/src/transform/check_consts/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,34 @@ use rustc_span::{Span, Symbol};

use super::ConstCx;

/// Emits an error if `op` is not allowed in the given const context.
pub fn non_const<O: NonConstOp>(ccx: &ConstCx<'_, '_>, op: O, span: Span) {
/// 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,
Status::Allowed => return false,

Status::Unstable(gate) if ccx.tcx.features().enabled(gate) => {
let unstable_in_stable = ccx.const_kind() == hir::ConstContext::ConstFn
&& ccx.tcx.features().enabled(sym::staged_api)
&& !ccx.tcx.has_attr(ccx.def_id.to_def_id(), sym::rustc_const_unstable)
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!("`#[feature({})]` cannot be depended on in a const-stable function", gate.as_str()))
.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,
)
.help("otherwise `#[allow_internal_unstable]` can be used to bypass stability checks")
.note("otherwise `#[allow_internal_unstable]` can be used to bypass stability checks")
.emit();
}

return;
return unstable_in_stable;
}

Status::Unstable(gate) => Some(gate),
Expand All @@ -45,12 +46,14 @@ pub fn non_const<O: NonConstOp>(ccx: &ConstCx<'_, '_>, op: O, span: Span) {

if ccx.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
ccx.tcx.sess.miri_unleashed_feature(span, gate);
return;
return false;
}

op.emit_error(ccx, span);
true
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Status {
Allowed,
Unstable(Symbol),
Expand All @@ -59,6 +62,8 @@ pub enum Status {

/// 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
Expand Down Expand Up @@ -93,6 +98,34 @@ pub trait NonConstOp: std::fmt::Debug {
}
}

#[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")
}
}

#[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")
}
}

/// A function call where the callee is a pointer.
#[derive(Debug)]
pub struct FnCallIndirect;
Expand Down Expand Up @@ -125,7 +158,8 @@ impl NonConstOp for FnCallNonConst {
///
/// Contains the name of the feature that would allow the use of this function.
#[derive(Debug)]
pub struct FnCallUnstable(pub DefId, pub Symbol);
pub struct FnCallUnstable(pub DefId, pub Option<Symbol>);

impl NonConstOp for FnCallUnstable {
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
let FnCallUnstable(def_id, feature) = *self;
Expand All @@ -134,13 +168,51 @@ impl NonConstOp for FnCallUnstable {
span,
&format!("`{}` is not yet stable as a const fn", ccx.tcx.def_path_str(def_id)),
);
if nightly_options::is_nightly_build() {
err.help(&format!("add `#![feature({})]` to the crate attributes to enable", feature));

if ccx.is_const_stable_const_fn() {
err.help("Const-stable functions can only call other const-stable functions");
} else if nightly_options::is_nightly_build() {
if let Some(feature) = feature {
err.help(&format!(
"add `#![feature({})]` to the crate attributes to enable",
feature
));
}
}
err.emit();
}
}

#[derive(Debug)]
pub struct FnPtrCast;
impl NonConstOp for FnPtrCast {
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, "function pointer casts are not allowed in const fn");
}
}

#[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 emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
mcf_emit_error(ccx, span, "const fn generators are unstable");
}
}

#[derive(Debug)]
pub struct HeapAllocation;
impl NonConstOp for HeapAllocation {
Expand Down Expand Up @@ -403,6 +475,24 @@ impl NonConstOp for ThreadLocalAccess {
}
}

#[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
} else {
Status::Unstable(sym::const_fn_transmute)
}
}

fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
mcf_emit_error(ccx, span, "can only call `transmute` from const items, not `const fn`");
}
}

#[derive(Debug)]
pub struct UnionAccess;
impl NonConstOp for UnionAccess {
Expand All @@ -425,3 +515,131 @@ impl NonConstOp for UnionAccess {
.emit();
}
}

/// See [#64992].
///
/// [#64992]: https://github.com/rust-lang/rust/issues/64992
#[derive(Debug)]
pub struct UnsizingCast;
impl NonConstOp for UnsizingCast {
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,
"unsizing casts to types besides slices are not allowed in const fn",
);
}
}

pub mod ty {
use super::*;

#[derive(Debug)]
pub struct MutRef;
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) {
mcf_emit_error(ccx, span, "mutable references in const fn are unstable");
}
}

#[derive(Debug)]
pub struct FnPtr;
impl NonConstOp for FnPtr {
const STOPS_CONST_CHECKING: bool = true;

fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
// FIXME: This attribute a hack to allow the specialization of the `futures` API. See
// #59739. We should have a proper feature gate for this.
if ccx.tcx.has_attr(ccx.def_id.to_def_id(), sym::rustc_allow_const_fn_ptr) {
Status::Allowed
} else {
mcf_status_in_item(ccx)
}
}

fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
mcf_emit_error(ccx, span, "function pointers in const fn are unstable");
}
}

#[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");
}
}

#[derive(Debug)]
pub struct TraitBound;
impl NonConstOp for TraitBound {
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,
"trait bounds other than `Sized` on const fn parameters are unstable",
);
}
}

/// A trait bound with the `?const Trait` opt-out
#[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) {
feature_err(
&ccx.tcx.sess.parse_sess,
sym::const_trait_bound_opt_out,
span,
"`?const Trait` syntax is unstable",
)
.emit()
}
}
}

fn mcf_status_in_item(ccx: &ConstCx<'_, '_>) -> Status {
if ccx.const_kind() != hir::ConstContext::ConstFn {
Status::Allowed
} else {
Status::Unstable(sym::const_fn)
}
}

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> \
for more information",
)
.help("add `#![feature(const_fn)]` to the crate attributes to enable")
.emit();
}
Loading

0 comments on commit 00569c3

Please sign in to comment.