Skip to content

Commit fc6b5d6

Browse files
committed
Auto merge of #67216 - ecstatic-morse:const-loop, r=oli-obk
Enable `loop` and `while` in constants behind a feature flag This PR is an initial implementation of #52000. It adds a `const_loop` feature gate, which allows `while` and `loop` expressions through both HIR and MIR const-checkers if enabled. `for` expressions remain forbidden by the HIR const-checker, since they desugar to a call to `IntoIterator::into_iter`, which will be rejected anyways. `while` loops also require [`#![feature(const_if_match)]`](#66507), since they have a conditional built into them. The diagnostics from the HIR const checker will suggest this to the user. r? @oli-obk cc @rust-lang/wg-const-eval
2 parents 6f82984 + faa52d1 commit fc6b5d6

26 files changed

+559
-127
lines changed

src/librustc_error_codes/error_codes/E0744.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Control-flow expressions are not allowed inside a const context.
33
At the moment, `if` and `match`, as well as the looping constructs `for`,
44
`while`, and `loop`, are forbidden inside a `const`, `static`, or `const fn`.
55

6-
```compile_fail,E0744
6+
```compile_fail,E0658
77
const _: i32 = {
88
let mut x = 0;
99
loop {

src/librustc_feature/active.rs

+14
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ macro_rules! declare_features {
5252
pub fn walk_feature_fields(&self, mut f: impl FnMut(&str, bool)) {
5353
$(f(stringify!($feature), self.$feature);)+
5454
}
55+
56+
/// Is the given feature enabled?
57+
///
58+
/// Panics if the symbol doesn't correspond to a declared feature.
59+
pub fn enabled(&self, feature: Symbol) -> bool {
60+
match feature {
61+
$( sym::$feature => self.$feature, )*
62+
63+
_ => panic!("`{}` was not listed in `declare_features`", feature),
64+
}
65+
}
5566
}
5667
};
5768
}
@@ -525,6 +536,9 @@ declare_features! (
525536
/// Allows using `&mut` in constant functions.
526537
(active, const_mut_refs, "1.41.0", Some(57349), None),
527538

539+
/// Allows the use of `loop` and `while` in constants.
540+
(active, const_loop, "1.41.0", Some(52000), None),
541+
528542
// -------------------------------------------------------------------------
529543
// feature-group-end: actual feature gates
530544
// -------------------------------------------------------------------------

src/librustc_mir/transform/check_consts/ops.rs

+4
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ impl NonConstOp for LiveDrop {
170170
#[derive(Debug)]
171171
pub struct Loop;
172172
impl NonConstOp for Loop {
173+
fn feature_gate(tcx: TyCtxt<'_>) -> Option<bool> {
174+
Some(tcx.features().const_loop)
175+
}
176+
173177
fn emit_error(&self, item: &Item<'_, '_>, span: Span) {
174178
// This should be caught by the HIR const-checker.
175179
item.tcx.sess.delay_span_bug(

src/librustc_mir/transform/qualify_min_const_fn.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -390,8 +390,12 @@ fn check_terminator(
390390
cleanup: _,
391391
} => check_operand(tcx, cond, span, def_id, body),
392392

393+
| TerminatorKind::FalseUnwind { .. }
394+
if feature_allowed(tcx, def_id, sym::const_loop)
395+
=> Ok(()),
396+
393397
TerminatorKind::FalseUnwind { .. } => {
394398
Err((span, "loops are not allowed in const fn".into()))
395-
},
399+
}
396400
}
397401
}

src/librustc_passes/check_const.rs

+68-19
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ use rustc::hir::map::Map;
1313
use rustc::hir;
1414
use rustc::ty::TyCtxt;
1515
use rustc::ty::query::Providers;
16-
use rustc_feature::Features;
16+
use rustc::session::config::nightly_options;
1717
use syntax::ast::Mutability;
1818
use syntax::feature_gate::feature_err;
1919
use syntax::span_err;
20-
use syntax_pos::{sym, Span};
20+
use syntax_pos::{sym, Span, Symbol};
2121
use rustc_error_codes::*;
2222

2323
use std::fmt;
@@ -37,18 +37,31 @@ impl NonConstExpr {
3737
}
3838
}
3939

40-
/// Returns `true` if all feature gates required to enable this expression are turned on, or
41-
/// `None` if there is no feature gate corresponding to this expression.
42-
fn is_feature_gate_enabled(self, features: &Features) -> Option<bool> {
40+
fn required_feature_gates(self) -> Option<&'static [Symbol]> {
4341
use hir::MatchSource::*;
44-
match self {
42+
use hir::LoopSource::*;
43+
44+
let gates: &[_] = match self {
4545
| Self::Match(Normal)
4646
| Self::Match(IfDesugar { .. })
4747
| Self::Match(IfLetDesugar { .. })
48-
=> Some(features.const_if_match),
48+
=> &[sym::const_if_match],
4949

50-
_ => None,
51-
}
50+
| Self::Loop(Loop)
51+
=> &[sym::const_loop],
52+
53+
| Self::Loop(While)
54+
| Self::Loop(WhileLet)
55+
| Self::Match(WhileDesugar)
56+
| Self::Match(WhileLetDesugar)
57+
=> &[sym::const_loop, sym::const_if_match],
58+
59+
// A `for` loop's desugaring contains a call to `IntoIterator::into_iter`,
60+
// so they are not yet allowed with `#![feature(const_loop)]`.
61+
_ => return None,
62+
};
63+
64+
Some(gates)
5265
}
5366
}
5467

@@ -120,11 +133,15 @@ impl<'tcx> CheckConstVisitor<'tcx> {
120133

121134
/// Emits an error when an unsupported expression is found in a const context.
122135
fn const_check_violated(&self, expr: NonConstExpr, span: Span) {
123-
match expr.is_feature_gate_enabled(self.tcx.features()) {
136+
let features = self.tcx.features();
137+
let required_gates = expr.required_feature_gates();
138+
match required_gates {
124139
// Don't emit an error if the user has enabled the requisite feature gates.
125-
Some(true) => return,
140+
Some(gates) if gates.iter().all(|&g| features.enabled(g)) => return,
126141

127-
// Users of `-Zunleash-the-miri-inside-of-you` must use feature gates when possible.
142+
// `-Zunleash-the-miri-inside-of-you` only works for expressions that don't have a
143+
// corresponding feature gate. This encourages nightly users to use feature gates when
144+
// possible.
128145
None if self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you => {
129146
self.tcx.sess.span_warn(span, "skipping const checks");
130147
return;
@@ -135,15 +152,47 @@ impl<'tcx> CheckConstVisitor<'tcx> {
135152

136153
let const_kind = self.const_kind
137154
.expect("`const_check_violated` may only be called inside a const context");
138-
139155
let msg = format!("`{}` is not allowed in a `{}`", expr.name(), const_kind);
140-
match expr {
141-
| NonConstExpr::Match(hir::MatchSource::Normal)
142-
| NonConstExpr::Match(hir::MatchSource::IfDesugar { .. })
143-
| NonConstExpr::Match(hir::MatchSource::IfLetDesugar { .. })
144-
=> feature_err(&self.tcx.sess.parse_sess, sym::const_if_match, span, &msg).emit(),
145156

146-
_ => span_err!(self.tcx.sess, span, E0744, "{}", msg),
157+
let required_gates = required_gates.unwrap_or(&[]);
158+
let missing_gates: Vec<_> = required_gates
159+
.iter()
160+
.copied()
161+
.filter(|&g| !features.enabled(g))
162+
.collect();
163+
164+
match missing_gates.as_slice() {
165+
&[] => span_err!(self.tcx.sess, span, E0744, "{}", msg),
166+
167+
// If the user enabled `#![feature(const_loop)]` but not `#![feature(const_if_match)]`,
168+
// explain why their `while` loop is being rejected.
169+
&[gate @ sym::const_if_match] if required_gates.contains(&sym::const_loop) => {
170+
feature_err(&self.tcx.sess.parse_sess, gate, span, &msg)
171+
.note("`#![feature(const_loop)]` alone is not sufficient, \
172+
since this loop expression contains an implicit conditional")
173+
.emit();
174+
}
175+
176+
&[missing_primary, ref missing_secondary @ ..] => {
177+
let mut err = feature_err(&self.tcx.sess.parse_sess, missing_primary, span, &msg);
178+
179+
// If multiple feature gates would be required to enable this expression, include
180+
// them as help messages. Don't emit a separate error for each missing feature gate.
181+
//
182+
// FIXME(ecstaticmorse): Maybe this could be incorporated into `feature_err`? This
183+
// is a pretty narrow case, however.
184+
if nightly_options::is_nightly_build() {
185+
for gate in missing_secondary {
186+
let note = format!(
187+
"add `#![feature({})]` to the crate attributes to enable",
188+
gate,
189+
);
190+
err.help(&note);
191+
}
192+
}
193+
194+
err.emit();
195+
}
147196
}
148197
}
149198

src/librustc_passes/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#![feature(in_band_lifetimes)]
1010
#![feature(nll)]
11+
#![feature(slice_patterns)]
1112

1213
#![recursion_limit="256"]
1314

src/libsyntax_pos/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ symbols! {
213213
const_indexing,
214214
const_in_array_repeat_expressions,
215215
const_let,
216+
const_loop,
216217
const_mut_refs,
217218
const_panic,
218219
const_raw_ptr_deref,

src/test/ui/closures/issue-52437.stderr

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ error: invalid label name `'static`
44
LL | [(); &(&'static: loop { |x| {}; }) as *const _ as usize]
55
| ^^^^^^^
66

7-
error[E0744]: `loop` is not allowed in a `const`
7+
error[E0658]: `loop` is not allowed in a `const`
88
--> $DIR/issue-52437.rs:2:13
99
|
1010
LL | [(); &(&'static: loop { |x| {}; }) as *const _ as usize]
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^
12+
|
13+
= note: for more information, see https://github.com/rust-lang/rust/issues/52000
14+
= help: add `#![feature(const_loop)]` to the crate attributes to enable
1215

1316
error[E0282]: type annotations needed
1417
--> $DIR/issue-52437.rs:2:30
@@ -18,5 +21,5 @@ LL | [(); &(&'static: loop { |x| {}; }) as *const _ as usize]
1821

1922
error: aborting due to 3 previous errors
2023

21-
Some errors have detailed explanations: E0282, E0744.
24+
Some errors have detailed explanations: E0282, E0658.
2225
For more information about an error, try `rustc --explain E0282`.

src/test/ui/consts/const-eval/infinite_loop.stderr

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error[E0744]: `while` is not allowed in a `const`
1+
error[E0658]: `while` is not allowed in a `const`
22
--> $DIR/infinite_loop.rs:7:9
33
|
44
LL | / while n != 0 {
@@ -8,6 +8,10 @@ LL | |
88
LL | |
99
LL | | }
1010
| |_________^
11+
|
12+
= note: for more information, see https://github.com/rust-lang/rust/issues/52000
13+
= help: add `#![feature(const_loop)]` to the crate attributes to enable
14+
= help: add `#![feature(const_if_match)]` to the crate attributes to enable
1115

1216
error[E0658]: `if` is not allowed in a `const`
1317
--> $DIR/infinite_loop.rs:9:17
@@ -39,5 +43,5 @@ LL | n = if n % 2 == 0 { n/2 } else { 3*n + 1 };
3943

4044
error: aborting due to 3 previous errors
4145

42-
Some errors have detailed explanations: E0080, E0658, E0744.
46+
Some errors have detailed explanations: E0080, E0658.
4347
For more information about an error, try `rustc --explain E0080`.

src/test/ui/consts/const-eval/issue-52442.stderr

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
error[E0744]: `loop` is not allowed in a `const`
1+
error[E0658]: `loop` is not allowed in a `const`
22
--> $DIR/issue-52442.rs:2:14
33
|
44
LL | [(); { &loop { break } as *const _ as usize } ];
55
| ^^^^^^^^^^^^^^
6+
|
7+
= note: for more information, see https://github.com/rust-lang/rust/issues/52000
8+
= help: add `#![feature(const_loop)]` to the crate attributes to enable
69

710
error[E0658]: casting pointers to integers in constants is unstable
811
--> $DIR/issue-52442.rs:2:13
@@ -21,5 +24,5 @@ LL | [(); { &loop { break } as *const _ as usize } ];
2124

2225
error: aborting due to 3 previous errors
2326

24-
Some errors have detailed explanations: E0080, E0658, E0744.
27+
Some errors have detailed explanations: E0080, E0658.
2528
For more information about an error, try `rustc --explain E0080`.

src/test/ui/consts/const-eval/issue-52475.stderr

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error[E0744]: `while` is not allowed in a `const`
1+
error[E0658]: `while` is not allowed in a `const`
22
--> $DIR/issue-52475.rs:6:9
33
|
44
LL | / while n < 5 {
@@ -7,6 +7,10 @@ LL | | n = (n + 1) % 5;
77
LL | | x = &0; // Materialize a new AllocId
88
LL | | }
99
| |_________^
10+
|
11+
= note: for more information, see https://github.com/rust-lang/rust/issues/52000
12+
= help: add `#![feature(const_loop)]` to the crate attributes to enable
13+
= help: add `#![feature(const_if_match)]` to the crate attributes to enable
1014

1115
warning: Constant evaluating a complex constant, this might take some time
1216
--> $DIR/issue-52475.rs:2:18
@@ -29,5 +33,5 @@ LL | n = (n + 1) % 5;
2933

3034
error: aborting due to 2 previous errors
3135

32-
Some errors have detailed explanations: E0080, E0744.
36+
Some errors have detailed explanations: E0080, E0658.
3337
For more information about an error, try `rustc --explain E0080`.
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1-
error[E0744]: `loop` is not allowed in a `const`
1+
error[E0658]: `loop` is not allowed in a `const`
22
--> $DIR/issue-62272.rs:7:17
33
|
44
LL | const FOO: () = loop { break; };
55
| ^^^^^^^^^^^^^^^
6+
|
7+
= note: for more information, see https://github.com/rust-lang/rust/issues/52000
8+
= help: add `#![feature(const_loop)]` to the crate attributes to enable
69

7-
error[E0744]: `loop` is not allowed in a `const`
10+
error[E0658]: `loop` is not allowed in a `const`
811
--> $DIR/issue-62272.rs:10:20
912
|
1013
LL | [FOO; { let x; loop { x = 5; break; } x }];
1114
| ^^^^^^^^^^^^^^^^^^^^^^
15+
|
16+
= note: for more information, see https://github.com/rust-lang/rust/issues/52000
17+
= help: add `#![feature(const_loop)]` to the crate attributes to enable
1218

1319
error: aborting due to 2 previous errors
1420

15-
For more information about this error, try `rustc --explain E0744`.
21+
For more information about this error, try `rustc --explain E0658`.
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
error[E0744]: `while` is not allowed in a `const`
1+
error[E0658]: `while` is not allowed in a `const`
22
--> $DIR/const-labeled-break.rs:10:19
33
|
44
LL | const CRASH: () = 'a: while break 'a {};
55
| ^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: for more information, see https://github.com/rust-lang/rust/issues/52000
8+
= help: add `#![feature(const_loop)]` to the crate attributes to enable
9+
= help: add `#![feature(const_if_match)]` to the crate attributes to enable
610

711
error: aborting due to previous error
812

9-
For more information about this error, try `rustc --explain E0744`.
13+
For more information about this error, try `rustc --explain E0658`.

0 commit comments

Comments
 (0)