Skip to content

Commit 3a84efd

Browse files
Add HIR pass to check for ifs and loops in a const
These high-level constructs get mapped to control-flow primitives by the time the MIR const-checker runs, making it hard to get the span for the erroneous expression.
1 parent 33b62be commit 3a84efd

File tree

3 files changed

+164
-0
lines changed

3 files changed

+164
-0
lines changed

src/librustc/query/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,11 @@ rustc_queries! {
329329
desc { |tcx| "checking for unstable API usage in {}", key.describe_as_module(tcx) }
330330
}
331331

332+
/// Checks the const bodies in the module for illegal operations (e.g. `if` or `loop`).
333+
query check_mod_const_bodies(key: DefId) -> () {
334+
desc { |tcx| "checking consts in {}", key.describe_as_module(tcx) }
335+
}
336+
332337
/// Checks the loops in the module.
333338
query check_mod_loops(key: DefId) -> () {
334339
desc { |tcx| "checking loops in {}", key.describe_as_module(tcx) }

src/librustc_passes/check_const.rs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
//! This pass checks the HIR bodies in a const context (e.g., `const`, `static`, `const fn`) for
2+
//! structured control flow (e.g. `if`, `while`), which is forbidden in a const context.
3+
//!
4+
//! By the time the MIR const-checker runs, these high-level constructs have been lowered to
5+
//! control-flow primitives (e.g., `Goto`, `SwitchInt`), making it tough to properly attribute
6+
//! errors. We still look for those primitives in the MIR const-checker to ensure nothing slips
7+
//! through, but errors for structured control flow in a `const` should be emitted here.
8+
9+
use rustc::hir::def_id::DefId;
10+
use rustc::hir::intravisit::{Visitor, NestedVisitorMap};
11+
use rustc::hir::map::Map;
12+
use rustc::hir;
13+
use rustc::session::Session;
14+
use rustc::ty::TyCtxt;
15+
use rustc::ty::query::Providers;
16+
use syntax::span_err;
17+
use syntax_pos::Span;
18+
19+
use std::fmt;
20+
21+
#[derive(Copy, Clone)]
22+
enum ConstKind {
23+
Static,
24+
StaticMut,
25+
ConstFn,
26+
Const,
27+
AnonConst,
28+
}
29+
30+
impl ConstKind {
31+
fn for_body(body: &hir::Body, hir_map: &Map<'_>) -> Option<Self> {
32+
let is_const_fn = |id| hir_map.fn_sig_by_hir_id(id).unwrap().header.is_const();
33+
34+
let owner = hir_map.body_owner(body.id());
35+
let const_kind = match hir_map.body_owner_kind(owner) {
36+
hir::BodyOwnerKind::Const => Self::Const,
37+
hir::BodyOwnerKind::Static(hir::Mutability::MutMutable) => Self::StaticMut,
38+
hir::BodyOwnerKind::Static(hir::Mutability::MutImmutable) => Self::Static,
39+
40+
hir::BodyOwnerKind::Fn if is_const_fn(owner) => Self::ConstFn,
41+
hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => return None,
42+
};
43+
44+
Some(const_kind)
45+
}
46+
}
47+
48+
impl fmt::Display for ConstKind {
49+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50+
let s = match self {
51+
Self::Static => "static",
52+
Self::StaticMut => "static mut",
53+
Self::Const | Self::AnonConst => "const",
54+
Self::ConstFn => "const fn",
55+
};
56+
57+
write!(f, "{}", s)
58+
}
59+
}
60+
61+
fn check_mod_const_bodies(tcx: TyCtxt<'_>, module_def_id: DefId) {
62+
if tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
63+
return;
64+
}
65+
66+
let mut vis = CheckConstVisitor::new(tcx);
67+
tcx.hir().visit_item_likes_in_module(module_def_id, &mut vis.as_deep_visitor());
68+
}
69+
70+
pub(crate) fn provide(providers: &mut Providers<'_>) {
71+
*providers = Providers {
72+
check_mod_const_bodies,
73+
..*providers
74+
};
75+
}
76+
77+
#[derive(Copy, Clone)]
78+
struct CheckConstVisitor<'tcx> {
79+
sess: &'tcx Session,
80+
hir_map: &'tcx Map<'tcx>,
81+
const_kind: Option<ConstKind>,
82+
}
83+
84+
impl<'tcx> CheckConstVisitor<'tcx> {
85+
fn new(tcx: TyCtxt<'tcx>) -> Self {
86+
CheckConstVisitor {
87+
sess: &tcx.sess,
88+
hir_map: tcx.hir(),
89+
const_kind: None,
90+
}
91+
}
92+
93+
/// Emits an error when an unsupported expression is found in a const context.
94+
fn const_check_violated(&self, bad_op: &str, span: Span) {
95+
let const_kind = self.const_kind
96+
.expect("`const_check_violated` may only be called inside a const context");
97+
98+
span_err!(self.sess, span, E0744, "`{}` is not allowed in a `{}`", bad_op, const_kind);
99+
}
100+
101+
/// Saves the parent `const_kind` before visiting a nested `Body` and restores it afterwards.
102+
fn recurse_into(&mut self, kind: Option<ConstKind>, f: impl FnOnce(&mut Self)) {
103+
let parent_kind = self.const_kind;
104+
self.const_kind = kind;
105+
f(self);
106+
self.const_kind = parent_kind;
107+
}
108+
}
109+
110+
impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> {
111+
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
112+
NestedVisitorMap::OnlyBodies(&self.hir_map)
113+
}
114+
115+
fn visit_anon_const(&mut self, anon: &'tcx hir::AnonConst) {
116+
let kind = Some(ConstKind::AnonConst);
117+
self.recurse_into(kind, |this| hir::intravisit::walk_anon_const(this, anon));
118+
}
119+
120+
fn visit_body(&mut self, body: &'tcx hir::Body) {
121+
let kind = ConstKind::for_body(body, self.hir_map);
122+
self.recurse_into(kind, |this| hir::intravisit::walk_body(this, body));
123+
}
124+
125+
fn visit_expr(&mut self, e: &'tcx hir::Expr) {
126+
match &e.kind {
127+
// Skip these checks if the current item is not const.
128+
_ if self.const_kind.is_none() => {}
129+
130+
hir::ExprKind::Loop(_, _, source) => {
131+
self.const_check_violated(source.name(), e.span);
132+
}
133+
134+
hir::ExprKind::Match(_, _, source) => {
135+
use hir::MatchSource::*;
136+
137+
let op = match source {
138+
Normal => Some("match"),
139+
IfDesugar { .. } | IfLetDesugar { .. } => Some("if"),
140+
TryDesugar => Some("?"),
141+
AwaitDesugar => Some(".await"),
142+
143+
// These are handled by `ExprKind::Loop` above.
144+
WhileDesugar | WhileLetDesugar | ForLoopDesugar => None,
145+
};
146+
147+
if let Some(op) = op {
148+
self.const_check_violated(op, e.span);
149+
}
150+
}
151+
152+
_ => {},
153+
}
154+
155+
hir::intravisit::walk_expr(self, e);
156+
}
157+
}

src/librustc_passes/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use rustc::ty::query::Providers;
2323
pub mod error_codes;
2424

2525
pub mod ast_validation;
26+
mod check_const;
2627
pub mod hir_stats;
2728
pub mod layout_test;
2829
pub mod loops;
@@ -32,6 +33,7 @@ mod liveness;
3233
mod intrinsicck;
3334

3435
pub fn provide(providers: &mut Providers<'_>) {
36+
check_const::provide(providers);
3537
entry::provide(providers);
3638
loops::provide(providers);
3739
liveness::provide(providers);

0 commit comments

Comments
 (0)