Skip to content

Commit 60b9901

Browse files
authoredSep 22, 2020
Rollup merge of #76807 - ecstatic-morse:const-checking-staged-api, r=oli-obk
Use const-checking to forbid use of unstable features in const-stable functions First step towards #76618. Currently this code isn't ever hit because `qualify_min_const_fn` runs first and catches pretty much everything. One exception is `const_precise_live_drops`, which does not use the newly added code since it runs as part of a separate pass. Also contains some unrelated refactoring, which is split into separate commits. r? @oli-obk
2 parents b3433c7 + abc7167 commit 60b9901

11 files changed

+172
-71
lines changed
 

‎compiler/rustc_mir/src/transform/check_consts/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
//! has interior mutability or needs to be dropped, as well as the visitor that emits errors when
55
//! it finds operations that are invalid in a certain context.
66
7+
use rustc_attr as attr;
78
use rustc_hir as hir;
89
use rustc_hir::def_id::{DefId, LocalDefId};
910
use rustc_middle::mir;
1011
use rustc_middle::ty::{self, TyCtxt};
12+
use rustc_span::Symbol;
1113

1214
pub use self::qualifs::Qualif;
1315

@@ -55,3 +57,9 @@ impl ConstCx<'mir, 'tcx> {
5557
pub fn is_lang_panic_fn(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool {
5658
Some(def_id) == tcx.lang_items().panic_fn() || Some(def_id) == tcx.lang_items().begin_panic_fn()
5759
}
60+
61+
pub fn allow_internal_unstable(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bool {
62+
let attrs = tcx.get_attrs(def_id);
63+
attr::allow_internal_unstable(&tcx.sess, attrs)
64+
.map_or(false, |mut features| features.any(|name| name == feature_gate))
65+
}

‎compiler/rustc_mir/src/transform/check_consts/ops.rs

+88-54
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Concrete error types for all operations which may be invalid in a certain const context.
22
3-
use rustc_errors::struct_span_err;
3+
use rustc_errors::{struct_span_err, Applicability};
44
use rustc_hir as hir;
55
use rustc_hir::def_id::DefId;
66
use rustc_session::config::nightly_options;
@@ -14,35 +14,54 @@ use super::ConstCx;
1414
pub fn non_const<O: NonConstOp>(ccx: &ConstCx<'_, '_>, op: O, span: Span) {
1515
debug!("illegal_op: op={:?}", op);
1616

17-
if op.is_allowed_in_item(ccx) {
18-
return;
19-
}
17+
let gate = match op.status_in_item(ccx) {
18+
Status::Allowed => return,
19+
20+
Status::Unstable(gate) if ccx.tcx.features().enabled(gate) => {
21+
let unstable_in_stable = ccx.const_kind() == hir::ConstContext::ConstFn
22+
&& ccx.tcx.features().enabled(sym::staged_api)
23+
&& !ccx.tcx.has_attr(ccx.def_id.to_def_id(), sym::rustc_const_unstable)
24+
&& !super::allow_internal_unstable(ccx.tcx, ccx.def_id.to_def_id(), gate);
25+
26+
if unstable_in_stable {
27+
ccx.tcx.sess
28+
.struct_span_err(span, &format!("`#[feature({})]` cannot be depended on in a const-stable function", gate.as_str()))
29+
.span_suggestion(
30+
ccx.body.span,
31+
"if it is not part of the public API, make this function unstably const",
32+
concat!(r#"#[rustc_const_unstable(feature = "...", issue = "...")]"#, '\n').to_owned(),
33+
Applicability::HasPlaceholders,
34+
)
35+
.help("otherwise `#[allow_internal_unstable]` can be used to bypass stability checks")
36+
.emit();
37+
}
38+
39+
return;
40+
}
41+
42+
Status::Unstable(gate) => Some(gate),
43+
Status::Forbidden => None,
44+
};
2045

2146
if ccx.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
22-
ccx.tcx.sess.miri_unleashed_feature(span, O::feature_gate());
47+
ccx.tcx.sess.miri_unleashed_feature(span, gate);
2348
return;
2449
}
2550

2651
op.emit_error(ccx, span);
2752
}
2853

54+
pub enum Status {
55+
Allowed,
56+
Unstable(Symbol),
57+
Forbidden,
58+
}
59+
2960
/// An operation that is not *always* allowed in a const context.
3061
pub trait NonConstOp: std::fmt::Debug {
31-
/// Returns the `Symbol` corresponding to the feature gate that would enable this operation,
32-
/// or `None` if such a feature gate does not exist.
33-
fn feature_gate() -> Option<Symbol> {
34-
None
35-
}
36-
37-
/// Returns `true` if this operation is allowed in the given item.
38-
///
39-
/// This check should assume that we are not in a non-const `fn`, where all operations are
40-
/// legal.
41-
///
42-
/// By default, it returns `true` if and only if this operation has a corresponding feature
43-
/// gate and that gate is enabled.
44-
fn is_allowed_in_item(&self, ccx: &ConstCx<'_, '_>) -> bool {
45-
Self::feature_gate().map_or(false, |gate| ccx.tcx.features().enabled(gate))
62+
/// Returns an enum indicating whether this operation is allowed within the given item.
63+
fn status_in_item(&self, _ccx: &ConstCx<'_, '_>) -> Status {
64+
Status::Forbidden
4665
}
4766

4867
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@@ -53,9 +72,13 @@ pub trait NonConstOp: std::fmt::Debug {
5372
"{} contains unimplemented expression type",
5473
ccx.const_kind()
5574
);
56-
if let Some(feat) = Self::feature_gate() {
57-
err.help(&format!("add `#![feature({})]` to the crate attributes to enable", feat));
75+
76+
if let Status::Unstable(gate) = self.status_in_item(ccx) {
77+
if !ccx.tcx.features().enabled(gate) && nightly_options::is_nightly_build() {
78+
err.help(&format!("add `#![feature({})]` to the crate attributes to enable", gate));
79+
}
5880
}
81+
5982
if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
6083
err.note(
6184
"A function call isn't allowed in the const's initialization expression \
@@ -147,7 +170,9 @@ pub struct InlineAsm;
147170
impl NonConstOp for InlineAsm {}
148171

149172
#[derive(Debug)]
150-
pub struct LiveDrop(pub Option<Span>);
173+
pub struct LiveDrop {
174+
pub dropped_at: Option<Span>,
175+
}
151176
impl NonConstOp for LiveDrop {
152177
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
153178
let mut diagnostic = struct_span_err!(
@@ -157,7 +182,7 @@ impl NonConstOp for LiveDrop {
157182
"destructors cannot be evaluated at compile-time"
158183
);
159184
diagnostic.span_label(span, format!("{}s cannot evaluate destructors", ccx.const_kind()));
160-
if let Some(span) = self.0 {
185+
if let Some(span) = self.dropped_at {
161186
diagnostic.span_label(span, "value is dropped here");
162187
}
163188
diagnostic.emit();
@@ -182,14 +207,13 @@ impl NonConstOp for CellBorrow {
182207
#[derive(Debug)]
183208
pub struct MutBorrow;
184209
impl NonConstOp for MutBorrow {
185-
fn is_allowed_in_item(&self, ccx: &ConstCx<'_, '_>) -> bool {
186-
// Forbid everywhere except in const fn
187-
ccx.const_kind() == hir::ConstContext::ConstFn
188-
&& ccx.tcx.features().enabled(Self::feature_gate().unwrap())
189-
}
190-
191-
fn feature_gate() -> Option<Symbol> {
192-
Some(sym::const_mut_refs)
210+
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
211+
// Forbid everywhere except in const fn with a feature gate
212+
if ccx.const_kind() == hir::ConstContext::ConstFn {
213+
Status::Unstable(sym::const_mut_refs)
214+
} else {
215+
Status::Forbidden
216+
}
193217
}
194218

195219
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@@ -201,15 +225,16 @@ impl NonConstOp for MutBorrow {
201225
&format!("mutable references are not allowed in {}s", ccx.const_kind()),
202226
)
203227
} else {
204-
struct_span_err!(
228+
let mut err = struct_span_err!(
205229
ccx.tcx.sess,
206230
span,
207231
E0764,
208232
"mutable references are not allowed in {}s",
209233
ccx.const_kind(),
210-
)
234+
);
235+
err.span_label(span, format!("`&mut` is only allowed in `const fn`"));
236+
err
211237
};
212-
err.span_label(span, "`&mut` is only allowed in `const fn`".to_string());
213238
if ccx.tcx.sess.teach(&err.get_code().unwrap()) {
214239
err.note(
215240
"References in statics and constants may only refer \
@@ -226,11 +251,17 @@ impl NonConstOp for MutBorrow {
226251
}
227252
}
228253

254+
// FIXME(ecstaticmorse): Unify this with `MutBorrow`. It has basically the same issues.
229255
#[derive(Debug)]
230256
pub struct MutAddressOf;
231257
impl NonConstOp for MutAddressOf {
232-
fn feature_gate() -> Option<Symbol> {
233-
Some(sym::const_mut_refs)
258+
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
259+
// Forbid everywhere except in const fn with a feature gate
260+
if ccx.const_kind() == hir::ConstContext::ConstFn {
261+
Status::Unstable(sym::const_mut_refs)
262+
} else {
263+
Status::Forbidden
264+
}
234265
}
235266

236267
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@@ -247,16 +278,16 @@ impl NonConstOp for MutAddressOf {
247278
#[derive(Debug)]
248279
pub struct MutDeref;
249280
impl NonConstOp for MutDeref {
250-
fn feature_gate() -> Option<Symbol> {
251-
Some(sym::const_mut_refs)
281+
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
282+
Status::Unstable(sym::const_mut_refs)
252283
}
253284
}
254285

255286
#[derive(Debug)]
256287
pub struct Panic;
257288
impl NonConstOp for Panic {
258-
fn feature_gate() -> Option<Symbol> {
259-
Some(sym::const_panic)
289+
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
290+
Status::Unstable(sym::const_panic)
260291
}
261292

262293
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@@ -289,8 +320,8 @@ impl NonConstOp for RawPtrComparison {
289320
#[derive(Debug)]
290321
pub struct RawPtrDeref;
291322
impl NonConstOp for RawPtrDeref {
292-
fn feature_gate() -> Option<Symbol> {
293-
Some(sym::const_raw_ptr_deref)
323+
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
324+
Status::Unstable(sym::const_raw_ptr_deref)
294325
}
295326

296327
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@@ -307,8 +338,8 @@ impl NonConstOp for RawPtrDeref {
307338
#[derive(Debug)]
308339
pub struct RawPtrToIntCast;
309340
impl NonConstOp for RawPtrToIntCast {
310-
fn feature_gate() -> Option<Symbol> {
311-
Some(sym::const_raw_ptr_to_usize_cast)
341+
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
342+
Status::Unstable(sym::const_raw_ptr_to_usize_cast)
312343
}
313344

314345
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@@ -326,8 +357,12 @@ impl NonConstOp for RawPtrToIntCast {
326357
#[derive(Debug)]
327358
pub struct StaticAccess;
328359
impl NonConstOp for StaticAccess {
329-
fn is_allowed_in_item(&self, ccx: &ConstCx<'_, '_>) -> bool {
330-
matches!(ccx.const_kind(), hir::ConstContext::Static(_))
360+
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
361+
if let hir::ConstContext::Static(_) = ccx.const_kind() {
362+
Status::Allowed
363+
} else {
364+
Status::Forbidden
365+
}
331366
}
332367

333368
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {
@@ -371,14 +406,13 @@ impl NonConstOp for ThreadLocalAccess {
371406
#[derive(Debug)]
372407
pub struct UnionAccess;
373408
impl NonConstOp for UnionAccess {
374-
fn is_allowed_in_item(&self, ccx: &ConstCx<'_, '_>) -> bool {
409+
fn status_in_item(&self, ccx: &ConstCx<'_, '_>) -> Status {
375410
// Union accesses are stable in all contexts except `const fn`.
376-
ccx.const_kind() != hir::ConstContext::ConstFn
377-
|| ccx.tcx.features().enabled(Self::feature_gate().unwrap())
378-
}
379-
380-
fn feature_gate() -> Option<Symbol> {
381-
Some(sym::const_fn_union)
411+
if ccx.const_kind() != hir::ConstContext::ConstFn {
412+
Status::Allowed
413+
} else {
414+
Status::Unstable(sym::const_fn_union)
415+
}
382416
}
383417

384418
fn emit_error(&self, ccx: &ConstCx<'_, '_>, span: Span) {

‎compiler/rustc_mir/src/transform/check_consts/post_drop_elaboration.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rustc_hir::def_id::LocalDefId;
22
use rustc_middle::mir::visit::Visitor;
33
use rustc_middle::mir::{self, BasicBlock, Location};
44
use rustc_middle::ty::TyCtxt;
5-
use rustc_span::Span;
5+
use rustc_span::{sym, Span};
66

77
use super::ops;
88
use super::qualifs::{NeedsDrop, Qualif};
@@ -11,7 +11,12 @@ use super::ConstCx;
1111

1212
/// Returns `true` if we should use the more precise live drop checker that runs after drop
1313
/// elaboration.
14-
pub fn checking_enabled(tcx: TyCtxt<'tcx>) -> bool {
14+
pub fn checking_enabled(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> bool {
15+
// Const-stable functions must always use the stable live drop checker.
16+
if tcx.features().staged_api && !tcx.has_attr(def_id.to_def_id(), sym::rustc_const_unstable) {
17+
return false;
18+
}
19+
1520
tcx.features().const_precise_live_drops
1621
}
1722

@@ -25,7 +30,7 @@ pub fn check_live_drops(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &mir::Body<
2530
return;
2631
}
2732

28-
if !checking_enabled(tcx) {
33+
if !checking_enabled(tcx, def_id) {
2934
return;
3035
}
3136

@@ -52,7 +57,7 @@ impl std::ops::Deref for CheckLiveDrops<'mir, 'tcx> {
5257

5358
impl CheckLiveDrops<'mir, 'tcx> {
5459
fn check_live_drop(&self, span: Span) {
55-
ops::non_const(self.ccx, ops::LiveDrop(None), span);
60+
ops::non_const(self.ccx, ops::LiveDrop { dropped_at: None }, span);
5661
}
5762
}
5863

‎compiler/rustc_mir/src/transform/check_consts/validation.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> {
551551
| TerminatorKind::DropAndReplace { place: dropped_place, .. } => {
552552
// If we are checking live drops after drop-elaboration, don't emit duplicate
553553
// errors here.
554-
if super::post_drop_elaboration::checking_enabled(self.tcx) {
554+
if super::post_drop_elaboration::checking_enabled(self.tcx, self.def_id) {
555555
return;
556556
}
557557

@@ -576,7 +576,7 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> {
576576

577577
if needs_drop {
578578
self.check_op_spanned(
579-
ops::LiveDrop(Some(terminator.source_info.span)),
579+
ops::LiveDrop { dropped_at: Some(terminator.source_info.span) },
580580
err_span,
581581
);
582582
}

‎compiler/rustc_mir/src/transform/qualify_min_const_fn.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use rustc_attr as attr;
21
use rustc_hir as hir;
32
use rustc_hir::def_id::DefId;
43
use rustc_middle::mir::*;
@@ -344,8 +343,7 @@ fn feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbol) -> bo
344343

345344
// However, we cannot allow stable `const fn`s to use unstable features without an explicit
346345
// opt-in via `allow_internal_unstable`.
347-
attr::allow_internal_unstable(&tcx.sess, &tcx.get_attrs(def_id))
348-
.map_or(false, |mut features| features.any(|name| name == feature_gate))
346+
super::check_consts::allow_internal_unstable(tcx, def_id, feature_gate)
349347
}
350348

351349
/// Returns `true` if the given library feature gate is allowed within the function with the given `DefId`.
@@ -364,8 +362,7 @@ pub fn lib_feature_allowed(tcx: TyCtxt<'tcx>, def_id: DefId, feature_gate: Symbo
364362

365363
// However, we cannot allow stable `const fn`s to use unstable features without an explicit
366364
// opt-in via `allow_internal_unstable`.
367-
attr::allow_internal_unstable(&tcx.sess, &tcx.get_attrs(def_id))
368-
.map_or(false, |mut features| features.any(|name| name == feature_gate))
365+
super::check_consts::allow_internal_unstable(tcx, def_id, feature_gate)
369366
}
370367

371368
fn check_terminator(

‎src/test/ui/consts/miri_unleashed/box.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ help: skipping check for `const_mut_refs` feature
2121
|
2222
LL | &mut *(box 0)
2323
| ^^^^^^^^^^^^^
24-
help: skipping check for `const_mut_refs` feature
24+
help: skipping check that does not even have a feature gate
2525
--> $DIR/box.rs:10:5
2626
|
2727
LL | &mut *(box 0)

‎src/test/ui/consts/miri_unleashed/mutable_references.stderr

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ LL | *OH_YES = 99;
66

77
warning: skipping const checks
88
|
9-
help: skipping check for `const_mut_refs` feature
9+
help: skipping check that does not even have a feature gate
1010
--> $DIR/mutable_references.rs:9:26
1111
|
1212
LL | static FOO: &&mut u32 = &&mut 42;
1313
| ^^^^^^^
14-
help: skipping check for `const_mut_refs` feature
14+
help: skipping check that does not even have a feature gate
1515
--> $DIR/mutable_references.rs:13:23
1616
|
1717
LL | static BAR: &mut () = &mut ();
1818
| ^^^^^^^
19-
help: skipping check for `const_mut_refs` feature
19+
help: skipping check that does not even have a feature gate
2020
--> $DIR/mutable_references.rs:18:28
2121
|
2222
LL | static BOO: &mut Foo<()> = &mut Foo(());
@@ -26,7 +26,7 @@ help: skipping check that does not even have a feature gate
2626
|
2727
LL | x: &UnsafeCell::new(42),
2828
| ^^^^^^^^^^^^^^^^^^^^
29-
help: skipping check for `const_mut_refs` feature
29+
help: skipping check that does not even have a feature gate
3030
--> $DIR/mutable_references.rs:30:27
3131
|
3232
LL | static OH_YES: &mut i32 = &mut 42;

‎src/test/ui/consts/miri_unleashed/mutable_references_err.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ help: skipping check that does not even have a feature gate
3030
|
3131
LL | const SNEAKY: &dyn Sync = &Synced { x: UnsafeCell::new(42) };
3232
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33-
help: skipping check for `const_mut_refs` feature
33+
help: skipping check that does not even have a feature gate
3434
--> $DIR/mutable_references_err.rs:30:25
3535
|
3636
LL | const BLUNT: &mut i32 = &mut 42;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#![stable(feature = "core", since = "1.6.0")]
2+
#![feature(staged_api)]
3+
#![feature(const_precise_live_drops, const_fn)]
4+
5+
enum Either<T, S> {
6+
Left(T),
7+
Right(S),
8+
}
9+
10+
impl<T> Either<T, T> {
11+
#[stable(feature = "rust1", since = "1.0.0")]
12+
#[rustc_const_stable(feature = "foo", since = "1.0.0")]
13+
pub const fn unwrap(self) -> T {
14+
//~^ ERROR destructors cannot be evaluated at compile-time
15+
match self {
16+
Self::Left(t) => t,
17+
Self::Right(t) => t,
18+
}
19+
}
20+
}
21+
22+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0493]: destructors cannot be evaluated at compile-time
2+
--> $DIR/stable-precise-live-drops-in-libcore.rs:13:25
3+
|
4+
LL | pub const fn unwrap(self) -> T {
5+
| ^^^^ constant functions cannot evaluate destructors
6+
...
7+
LL | }
8+
| - value is dropped here
9+
10+
error: aborting due to previous error
11+
12+
For more information about this error, try `rustc --explain E0493`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// check-pass
2+
3+
#![stable(feature = "core", since = "1.6.0")]
4+
#![feature(staged_api)]
5+
#![feature(const_precise_live_drops)]
6+
7+
enum Either<T, S> {
8+
Left(T),
9+
Right(S),
10+
}
11+
12+
impl<T> Either<T, T> {
13+
#[stable(feature = "rust1", since = "1.0.0")]
14+
#[rustc_const_unstable(feature = "foo", issue = "none")]
15+
pub const fn unwrap(self) -> T {
16+
match self {
17+
Self::Left(t) => t,
18+
Self::Right(t) => t,
19+
}
20+
}
21+
}
22+
23+
fn main() {}

0 commit comments

Comments
 (0)
Please sign in to comment.