-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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 a HIR pass to check consts for if
, loop
, etc.
#66170
Changes from all commits
8b7d2bc
33b62be
3a84efd
267733b
92386e8
67336bb
3ce8ca4
281e898
eff83e5
0123cbd
70aa781
86734b1
5e048da
7552bd6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
//! This pass checks HIR bodies that may be evaluated at compile-time (e.g., `const`, `static`, | ||
//! `const fn`) for structured control flow (e.g. `if`, `while`), which is forbidden in a const | ||
//! context. | ||
//! | ||
//! By the time the MIR const-checker runs, these high-level constructs have been lowered to | ||
//! control-flow primitives (e.g., `Goto`, `SwitchInt`), making it tough to properly attribute | ||
//! errors. We still look for those primitives in the MIR const-checker to ensure nothing slips | ||
//! through, but errors for structured control flow in a `const` should be emitted here. | ||
|
||
use rustc::hir::def_id::DefId; | ||
use rustc::hir::intravisit::{Visitor, NestedVisitorMap}; | ||
use rustc::hir::map::Map; | ||
use rustc::hir; | ||
use rustc::session::Session; | ||
use rustc::ty::TyCtxt; | ||
use rustc::ty::query::Providers; | ||
use syntax::ast::Mutability; | ||
use syntax::span_err; | ||
use syntax_pos::Span; | ||
|
||
use std::fmt; | ||
|
||
#[derive(Copy, Clone)] | ||
enum ConstKind { | ||
Static, | ||
StaticMut, | ||
ConstFn, | ||
Const, | ||
AnonConst, | ||
} | ||
|
||
impl ConstKind { | ||
fn for_body(body: &hir::Body, hir_map: &Map<'_>) -> Option<Self> { | ||
let is_const_fn = |id| hir_map.fn_sig_by_hir_id(id).unwrap().header.is_const(); | ||
|
||
let owner = hir_map.body_owner(body.id()); | ||
let const_kind = match hir_map.body_owner_kind(owner) { | ||
hir::BodyOwnerKind::Const => Self::Const, | ||
hir::BodyOwnerKind::Static(Mutability::Mutable) => Self::StaticMut, | ||
hir::BodyOwnerKind::Static(Mutability::Immutable) => Self::Static, | ||
|
||
hir::BodyOwnerKind::Fn if is_const_fn(owner) => Self::ConstFn, | ||
hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => return None, | ||
}; | ||
|
||
Some(const_kind) | ||
} | ||
} | ||
|
||
impl fmt::Display for ConstKind { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
let s = match self { | ||
Self::Static => "static", | ||
Self::StaticMut => "static mut", | ||
Self::Const | Self::AnonConst => "const", | ||
Self::ConstFn => "const fn", | ||
}; | ||
|
||
write!(f, "{}", s) | ||
} | ||
} | ||
|
||
fn check_mod_const_bodies(tcx: TyCtxt<'_>, module_def_id: DefId) { | ||
let mut vis = CheckConstVisitor::new(tcx); | ||
tcx.hir().visit_item_likes_in_module(module_def_id, &mut vis.as_deep_visitor()); | ||
} | ||
|
||
pub(crate) fn provide(providers: &mut Providers<'_>) { | ||
Centril marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*providers = Providers { | ||
check_mod_const_bodies, | ||
..*providers | ||
}; | ||
} | ||
|
||
#[derive(Copy, Clone)] | ||
struct CheckConstVisitor<'tcx> { | ||
sess: &'tcx Session, | ||
hir_map: &'tcx Map<'tcx>, | ||
const_kind: Option<ConstKind>, | ||
} | ||
|
||
impl<'tcx> CheckConstVisitor<'tcx> { | ||
fn new(tcx: TyCtxt<'tcx>) -> Self { | ||
CheckConstVisitor { | ||
sess: &tcx.sess, | ||
hir_map: tcx.hir(), | ||
const_kind: None, | ||
} | ||
} | ||
|
||
/// Emits an error when an unsupported expression is found in a const context. | ||
fn const_check_violated(&self, bad_op: &str, span: Span) { | ||
if self.sess.opts.debugging_opts.unleash_the_miri_inside_of_you { | ||
self.sess.span_warn(span, "skipping const checks"); | ||
return; | ||
} | ||
|
||
let const_kind = self.const_kind | ||
.expect("`const_check_violated` may only be called inside a const context"); | ||
|
||
span_err!(self.sess, span, E0744, "`{}` is not allowed in a `{}`", bad_op, const_kind); | ||
} | ||
|
||
/// Saves the parent `const_kind` before calling `f` and restores it afterwards. | ||
fn recurse_into(&mut self, kind: Option<ConstKind>, f: impl FnOnce(&mut Self)) { | ||
let parent_kind = self.const_kind; | ||
self.const_kind = kind; | ||
f(self); | ||
self.const_kind = parent_kind; | ||
} | ||
} | ||
|
||
impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> { | ||
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> { | ||
NestedVisitorMap::OnlyBodies(&self.hir_map) | ||
} | ||
|
||
fn visit_anon_const(&mut self, anon: &'tcx hir::AnonConst) { | ||
let kind = Some(ConstKind::AnonConst); | ||
self.recurse_into(kind, |this| hir::intravisit::walk_anon_const(this, anon)); | ||
} | ||
|
||
fn visit_body(&mut self, body: &'tcx hir::Body) { | ||
let kind = ConstKind::for_body(body, self.hir_map); | ||
self.recurse_into(kind, |this| hir::intravisit::walk_body(this, body)); | ||
} | ||
|
||
fn visit_expr(&mut self, e: &'tcx hir::Expr) { | ||
match &e.kind { | ||
// Skip the following checks if we are not currently in a const context. | ||
_ if self.const_kind.is_none() => {} | ||
|
||
hir::ExprKind::Loop(_, _, source) => { | ||
self.const_check_violated(source.name(), e.span); | ||
} | ||
|
||
hir::ExprKind::Match(_, _, source) => { | ||
use hir::MatchSource::*; | ||
|
||
let op = match source { | ||
Normal => Some("match"), | ||
IfDesugar { .. } | IfLetDesugar { .. } => Some("if"), | ||
TryDesugar => Some("?"), | ||
AwaitDesugar => Some(".await"), | ||
|
||
// These are handled by `ExprKind::Loop` above. | ||
WhileDesugar | WhileLetDesugar | ForLoopDesugar => None, | ||
}; | ||
|
||
if let Some(op) = op { | ||
self.const_check_violated(op, e.span); | ||
} | ||
} | ||
|
||
_ => {}, | ||
} | ||
|
||
hir::intravisit::walk_expr(self, e); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -626,6 +626,28 @@ async fn foo() {} | |
Switch to the Rust 2018 edition to use `async fn`. | ||
"##, | ||
|
||
E0744: r##" | ||
Control-flow expressions are not allowed inside a const context. | ||
|
||
At the moment, `if` and `match`, as well as the looping constructs `for`, | ||
`while`, and `loop`, are forbidden inside a `const`, `static`, or `const fn`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll do this in a follow-up PR since the bulk of this one is ready. |
||
|
||
```compile_fail,E0744 | ||
const _: i32 = { | ||
let mut x = 0; | ||
loop { | ||
x += 1; | ||
if x == 4 { | ||
break; | ||
} | ||
} | ||
|
||
x | ||
}; | ||
``` | ||
|
||
"##, | ||
|
||
; | ||
E0226, // only a single explicit lifetime bound is permitted | ||
E0472, // asm! is unsupported on this target | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,14 @@ | ||
fn main() { | ||
[(); & { loop { continue } } ]; //~ ERROR mismatched types | ||
//~^ ERROR `loop` is not allowed in a `const` | ||
[(); loop { break }]; //~ ERROR mismatched types | ||
//~^ ERROR `loop` is not allowed in a `const` | ||
[(); {while true {break}; 0}]; | ||
//~^ ERROR constant contains unimplemented expression type | ||
//~| ERROR constant contains unimplemented expression type | ||
//~^ ERROR `while` is not allowed in a `const` | ||
//~| WARN denote infinite loops with | ||
[(); { for _ in 0usize.. {}; 0}]; | ||
//~^ ERROR calls in constants are limited to constant functions | ||
//~| ERROR `for` is not allowed in a `const` | ||
//~| ERROR references in constants may only refer to immutable values | ||
//~| ERROR constant contains unimplemented expression type | ||
//~| ERROR constant contains unimplemented expression type | ||
//~| ERROR evaluation of constant value failed | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,7 @@ struct Project; | |
struct Value; | ||
|
||
static settings_dir: String = format!(""); | ||
//~^ ERROR [E0019] | ||
//~| ERROR [E0015] | ||
//~| ERROR [E0015] | ||
//~^ ERROR `match` is not allowed in a `static` | ||
|
||
fn from_string(_: String) -> Value { | ||
Value | ||
|
@@ -13,7 +11,6 @@ fn set_editor(_: Value) {} | |
|
||
fn main() { | ||
let settings_data = from_string(settings_dir); | ||
//~^ ERROR cannot move out of static item `settings_dir` [E0507] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's unfortunate that this error no longer occurs. I believe it's simply because the compiler stops working before the move checker runs? |
||
let args: i32 = 0; | ||
|
||
match args { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aside: I noticed that the categorization in this module wrt.
Other
,TypeChecking
, and friends is rather messy and there doesn't seem to be a lot of logic to it? -- Maybe another C-cleanup issue...? (cc @Zoxc & @nikomatsakis)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this categorization was added as a very coarse grouping for profiling purposes IIRC, we can certainly clean things up here. cc @wesleywiser
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cleanups wrt categorization are definitely appreciated. I did the initial pass ~1yr ago and I don't think they've changed substantially since that time. Feel free to assign me to any such cleanup PRs.