rustc: const-qualify const fn function and method calls.

This commit is contained in:
Eduard Burtescu 2015-02-25 22:06:08 +02:00 committed by Niko Matsakis
parent af3795721c
commit 1bd420555e
8 changed files with 288 additions and 57 deletions

View file

@ -262,3 +262,5 @@ pub const tag_item_super_predicates: usize = 0xa3;
pub const tag_defaulted_trait: usize = 0xa4;
pub const tag_impl_coerce_unsized_kind: usize = 0xa5;
pub const tag_items_data_item_constness: usize = 0xa6;

View file

@ -384,6 +384,11 @@ pub fn is_typedef(cstore: &cstore::CStore, did: ast::DefId) -> bool {
decoder::is_typedef(&*cdata, did.node)
}
pub fn is_const_fn(cstore: &cstore::CStore, did: ast::DefId) -> bool {
let cdata = cstore.get_crate_data(did.krate);
decoder::is_const_fn(&*cdata, did.node)
}
pub fn get_stability(cstore: &cstore::CStore,
def: ast::DefId)
-> Option<attr::Stability> {

View file

@ -178,6 +178,19 @@ fn item_visibility(item: rbml::Doc) -> ast::Visibility {
}
}
fn fn_constness(item: rbml::Doc) -> ast::Constness {
match reader::maybe_get_doc(item, tag_items_data_item_constness) {
None => ast::Constness::NotConst,
Some(constness_doc) => {
match reader::doc_as_u8(constness_doc) as char {
'c' => ast::Constness::Const,
'n' => ast::Constness::NotConst,
_ => panic!("unknown constness character")
}
}
}
}
fn item_sort(item: rbml::Doc) -> Option<char> {
let mut ret = None;
reader::tagged_docs(item, tag_item_trait_item_sort, |doc| {
@ -1525,6 +1538,14 @@ pub fn is_typedef(cdata: Cmd, id: ast::NodeId) -> bool {
}
}
pub fn is_const_fn(cdata: Cmd, id: ast::NodeId) -> bool {
let item_doc = lookup_item(id, cdata.data());
match fn_constness(item_doc) {
ast::Constness::Const => true,
ast::Constness::NotConst => false,
}
}
fn doc_generics<'tcx>(base_doc: rbml::Doc,
tcx: &ty::ctxt<'tcx>,
cdata: Cmd,

View file

@ -581,6 +581,16 @@ fn encode_visibility(rbml_w: &mut Encoder, visibility: ast::Visibility) {
rbml_w.wr_tagged_u8(tag_items_data_item_visibility, ch as u8);
}
fn encode_constness(rbml_w: &mut Encoder, constness: ast::Constness) {
rbml_w.start_tag(tag_items_data_item_constness);
let ch = match constness {
ast::Constness::Const => 'c',
ast::Constness::NotConst => 'n',
};
rbml_w.wr_str(&ch.to_string());
rbml_w.end_tag();
}
fn encode_explicit_self(rbml_w: &mut Encoder,
explicit_self: &ty::ExplicitSelfCategory) {
let tag = tag_item_trait_method_explicit_self;
@ -867,10 +877,14 @@ fn encode_info_for_method<'a, 'tcx>(ecx: &EncodeContext<'a, 'tcx>,
encode_attributes(rbml_w, &impl_item.attrs);
let scheme = ty::lookup_item_type(ecx.tcx, m.def_id);
let any_types = !scheme.generics.types.is_empty();
if any_types || is_default_impl || attr::requests_inline(&impl_item.attrs) {
let needs_inline = any_types || is_default_impl ||
attr::requests_inline(&impl_item.attrs);
let constness = ast_method.pe_constness();
if needs_inline || constness == ast::Constness::Const {
encode_inlined_item(ecx, rbml_w, IIImplItemRef(local_def(parent_id),
impl_item));
}
encode_constness(rbml_w, constness);
if !any_types {
encode_symbol(ecx, rbml_w, m.def_id.node);
}
@ -1049,7 +1063,7 @@ fn encode_info_for_item(ecx: &EncodeContext,
encode_stability(rbml_w, stab);
rbml_w.end_tag();
}
ast::ItemFn(ref decl, _, _, _, ref generics, _) => {
ast::ItemFn(ref decl, _, constness, _, ref generics, _) => {
add_to_index(item, rbml_w, index);
rbml_w.start_tag(tag_items_data_item);
encode_def_id(rbml_w, def_id);
@ -1059,12 +1073,14 @@ fn encode_info_for_item(ecx: &EncodeContext,
encode_name(rbml_w, item.ident.name);
encode_path(rbml_w, path);
encode_attributes(rbml_w, &item.attrs);
if tps_len > 0 || attr::requests_inline(&item.attrs) {
let needs_inline = tps_len > 0 || attr::requests_inline(&item.attrs);
if needs_inline || constness == ast::Constness::Const {
encode_inlined_item(ecx, rbml_w, IIItemRef(item));
}
if tps_len == 0 {
encode_symbol(ecx, rbml_w, item.id);
}
encode_constness(rbml_w, constness);
encode_visibility(rbml_w, vis);
encode_stability(rbml_w, stab);
encode_method_argument_names(rbml_w, &**decl);

View file

@ -36,6 +36,7 @@ use util::nodemap::NodeMap;
use util::ppaux::Repr;
use syntax::ast;
use syntax::ast_util::PostExpansionMethod;
use syntax::codemap::Span;
use syntax::visit::{self, Visitor};
@ -79,6 +80,7 @@ bitflags! {
#[derive(Copy, Clone, Eq, PartialEq)]
enum Mode {
Const,
ConstFn,
Static,
StaticMut,
@ -136,10 +138,87 @@ impl<'a, 'tcx> CheckCrateVisitor<'a, 'tcx> {
})
}
fn fn_like(&mut self,
fk: visit::FnKind,
fd: &ast::FnDecl,
b: &ast::Block,
s: Span,
fn_id: ast::NodeId)
-> ConstQualif {
match self.tcx.const_qualif_map.borrow_mut().entry(fn_id) {
Entry::Occupied(entry) => return *entry.get(),
Entry::Vacant(entry) => {
// Prevent infinite recursion on re-entry.
entry.insert(PURE_CONST);
}
}
let mode = match fk {
visit::FkItemFn(_, _, _, ast::Constness::Const, _) => {
Mode::ConstFn
}
visit::FkMethod(_, _, m) => {
if m.pe_constness() == ast::Constness::Const {
Mode::ConstFn
} else {
Mode::Var
}
}
_ => Mode::Var
};
// Ensure the arguments are simple, not mutable/by-ref or patterns.
if mode == Mode::ConstFn {
for arg in &fd.inputs {
match arg.pat.node {
ast::PatIdent(ast::BindByValue(ast::MutImmutable), _, None) => {}
_ => {
span_err!(self.tcx.sess, arg.pat.span, E0022,
"arguments of constant functions can only \
be immutable by-value bindings");
}
}
}
}
let qualif = self.with_mode(mode, |this| {
this.with_euv(Some(fn_id), |euv| euv.walk_fn(fd, b));
visit::walk_fn(this, fk, fd, b, s);
this.qualif
});
// Keep only bits that aren't affected by function body (NON_ZERO_SIZED),
// and bits that don't change semantics, just optimizations (PREFER_IN_PLACE).
let qualif = qualif & (NON_ZERO_SIZED | PREFER_IN_PLACE);
self.tcx.const_qualif_map.borrow_mut().insert(fn_id, qualif);
qualif
}
fn add_qualif(&mut self, qualif: ConstQualif) {
self.qualif = self.qualif | qualif;
}
/// Returns true if the call is to a const fn or method.
fn handle_const_fn_call(&mut self, def_id: ast::DefId, ret_ty: Ty<'tcx>) -> bool {
if let Some(fn_like) = const_eval::lookup_const_fn_by_id(self.tcx, def_id) {
let qualif = self.fn_like(fn_like.kind(),
fn_like.decl(),
fn_like.body(),
fn_like.span(),
fn_like.id());
self.add_qualif(qualif);
if ty::type_contents(self.tcx, ret_ty).interior_unsafe() {
self.add_qualif(MUTABLE_MEM);
}
true
} else {
false
}
}
fn record_borrow(&mut self, id: ast::NodeId, mutbl: ast::Mutability) {
match self.rvalue_borrows.entry(id) {
Entry::Occupied(mut entry) => {
@ -158,6 +237,7 @@ impl<'a, 'tcx> CheckCrateVisitor<'a, 'tcx> {
fn msg(&self) -> &'static str {
match self.mode {
Mode::Const => "constant",
Mode::ConstFn => "constant function",
Mode::StaticMut | Mode::Static => "static",
Mode::Var => unreachable!(),
}
@ -251,9 +331,7 @@ impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> {
b: &'v ast::Block,
s: Span,
fn_id: ast::NodeId) {
assert!(self.mode == Mode::Var);
self.with_euv(Some(fn_id), |euv| euv.walk_fn(fd, b));
visit::walk_fn(self, fk, fd, b, s);
self.fn_like(fk, fd, b, s, fn_id);
}
fn visit_pat(&mut self, p: &ast::Pat) {
@ -269,6 +347,35 @@ impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> {
}
}
fn visit_block(&mut self, block: &ast::Block) {
// Check all statements in the block
for stmt in &block.stmts {
let span = match stmt.node {
ast::StmtDecl(ref decl, _) => {
match decl.node {
ast::DeclLocal(_) => decl.span,
// Item statements are allowed
ast::DeclItem(_) => continue
}
}
ast::StmtExpr(ref expr, _) => expr.span,
ast::StmtSemi(ref semi, _) => semi.span,
ast::StmtMac(..) => {
self.tcx.sess.span_bug(stmt.span, "unexpanded statement \
macro in const?!")
}
};
self.add_qualif(NOT_CONST);
if self.mode != Mode::Var {
span_err!(self.tcx.sess, span, E0016,
"blocks in {}s are limited to items and \
tail expressions", self.msg());
}
}
visit::walk_block(self, block);
}
fn visit_expr(&mut self, ex: &ast::Expr) {
let mut outer = self.qualif;
self.qualif = ConstQualif::empty();
@ -473,10 +580,10 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>,
Some(def::DefStatic(..)) => {
match v.mode {
Mode::Static | Mode::StaticMut => {}
Mode::Const => {
Mode::Const | Mode::ConstFn => {
span_err!(v.tcx.sess, e.span, E0013,
"constants cannot refer to other statics, \
insert an intermediate constant instead");
"{}s cannot refer to other statics, insert \
an intermediate constant instead", v.msg());
}
Mode::Var => v.add_qualif(ConstQualif::NOT_CONST)
}
@ -493,6 +600,10 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>,
doesn't point to a constant");
}
}
Some(def::DefLocal(_)) if v.mode == Mode::ConstFn => {
// Sadly, we can't determine whether the types are zero-sized.
v.add_qualif(NOT_CONST | NON_ZERO_SIZED);
}
def => {
v.add_qualif(ConstQualif::NOT_CONST);
if v.mode != Mode::Var {
@ -517,19 +628,26 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>,
};
}
let def = v.tcx.def_map.borrow().get(&callee.id).map(|d| d.full_def());
match def {
Some(def::DefStruct(..)) => {}
let is_const = match def {
Some(def::DefStruct(..)) => true,
Some(def::DefVariant(..)) => {
// Count the discriminator.
v.add_qualif(ConstQualif::NON_ZERO_SIZED);
true
}
_ => {
v.add_qualif(ConstQualif::NOT_CONST);
if v.mode != Mode::Var {
span_err!(v.tcx.sess, e.span, E0015,
"function calls in {}s are limited to \
struct and enum constructors", v.msg());
}
Some(def::DefMethod(did, def::FromImpl(_))) |
Some(def::DefFn(did, _)) => {
v.handle_const_fn_call(did, node_ty)
}
_ => false
};
if !is_const {
v.add_qualif(ConstQualif::NOT_CONST);
if v.mode != Mode::Var {
span_err!(v.tcx.sess, e.span, E0015,
"function calls in {}s are limited to \
constant functions, \
struct and enum constructors", v.msg());
}
}
}
@ -538,27 +656,28 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>,
let mut block_span_err = |span| {
v.add_qualif(ConstQualif::NOT_CONST);
if v.mode != Mode::Var {
span_err!(v.tcx.sess, span, E0016,
"blocks in {}s are limited to items and \
tail expressions", v.msg());
span_err!(v.tcx.sess, e.span, E0015,
"function calls in {}s are limited to \
constant functions, \
struct and enum constructors", v.msg());
}
}
}
ast::ExprMethodCall(..) => {
let method_did = match v.tcx.method_map.borrow()[method_call].origin {
ty::MethodStatic(did) => Some(did),
_ => None
};
for stmt in &block.stmts {
match stmt.node {
ast::StmtDecl(ref decl, _) => {
match decl.node {
ast::DeclLocal(_) => block_span_err(decl.span),
// Item statements are allowed
ast::DeclItem(_) => {}
}
}
ast::StmtExpr(ref expr, _) => block_span_err(expr.span),
ast::StmtSemi(ref semi, _) => block_span_err(semi.span),
ast::StmtMac(..) => {
v.tcx.sess.span_bug(e.span, "unexpanded statement \
macro in const?!")
}
let is_const = match method_did {
Some(did) => v.handle_const_fn_call(did, node_ty),
None => false
};
if !is_const {
v.add_qualif(NOT_CONST);
if v.mode != Mode::Var {
span_err!(v.tcx.sess, e.span, E0021,
"method calls in {}s are limited to \
constant inherent methods", v.msg());
}
}
}
@ -579,7 +698,7 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>,
}
ast::ExprClosure(..) => {
// Paths in constant constexts cannot refer to local variables,
// Paths in constant contexts cannot refer to local variables,
// as there are none, and thus closures can't have upvars there.
if ty::with_freevars(v.tcx, e.id, |fv| !fv.is_empty()) {
assert!(v.mode == Mode::Var,
@ -588,6 +707,7 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>,
}
}
ast::ExprBlock(_) |
ast::ExprUnary(..) |
ast::ExprBinary(..) |
ast::ExprIndex(..) |
@ -616,8 +736,7 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>,
// Miscellaneous expressions that could be implemented.
ast::ExprRange(..) |
// Various other expressions.
ast::ExprMethodCall(..) |
// Expressions with side-effects.
ast::ExprAssign(..) |
ast::ExprAssignOp(..) |
ast::ExprInlineAsm(_) |

View file

@ -24,11 +24,13 @@ use util::num::ToPrimitive;
use util::ppaux::Repr;
use syntax::ast::{self, Expr};
use syntax::ast_map::blocks::FnLikeNode;
use syntax::ast_util::{self, PostExpansionMethod};
use syntax::codemap::Span;
use syntax::feature_gate;
use syntax::parse::token::InternedString;
use syntax::ptr::P;
use syntax::{ast_map, ast_util, codemap};
use syntax::{ast_map, codemap, visit};
use std::borrow::{Cow, IntoCow};
use std::num::wrapping::OverflowingOps;
@ -198,6 +200,63 @@ pub fn lookup_const_by_id<'a, 'tcx: 'a>(tcx: &'a ty::ctxt<'tcx>,
}
}
fn inline_const_fn_from_external_crate(tcx: &ty::ctxt, def_id: ast::DefId)
-> Option<ast::NodeId> {
match tcx.extern_const_fns.borrow().get(&def_id) {
Some(&ast::DUMMY_NODE_ID) => return None,
Some(&fn_id) => return Some(fn_id),
None => {}
}
if !csearch::is_const_fn(&tcx.sess.cstore, def_id) {
tcx.extern_const_fns.borrow_mut().insert(def_id, ast::DUMMY_NODE_ID);
return None;
}
let fn_id = match csearch::maybe_get_item_ast(tcx, def_id,
box |a, b, c, d| astencode::decode_inlined_item(a, b, c, d)) {
csearch::FoundAst::Found(&ast::IIItem(ref item)) => Some(item.id),
csearch::FoundAst::Found(&ast::IIImplItem(_, ast::MethodImplItem(ref m))) => Some(m.id),
_ => None
};
tcx.extern_const_fns.borrow_mut().insert(def_id,
fn_id.unwrap_or(ast::DUMMY_NODE_ID));
fn_id
}
pub fn lookup_const_fn_by_id<'a>(tcx: &'a ty::ctxt, def_id: ast::DefId)
-> Option<FnLikeNode<'a>> {
let fn_id = if !ast_util::is_local(def_id) {
if let Some(fn_id) = inline_const_fn_from_external_crate(tcx, def_id) {
fn_id
} else {
return None;
}
} else {
def_id.node
};
let fn_like = match FnLikeNode::from_node(tcx.map.get(fn_id)) {
Some(fn_like) => fn_like,
None => return None
};
match fn_like.kind() {
visit::FkItemFn(_, _, _, ast::Constness::Const, _) => {
Some(fn_like)
}
visit::FkMethod(_, _, m) => {
if m.pe_constness() == ast::Constness::Const {
Some(fn_like)
} else {
None
}
}
_ => None
}
}
#[derive(Clone, PartialEq)]
pub enum const_val {
const_float(f64),

View file

@ -775,10 +775,10 @@ pub struct ctxt<'tcx> {
/// Borrows
pub upvar_capture_map: RefCell<UpvarCaptureMap>,
/// These two caches are used by const_eval when decoding external statics
/// and variants that are found.
/// These caches are used by const_eval when decoding external constants.
pub extern_const_statics: RefCell<DefIdMap<ast::NodeId>>,
pub extern_const_variants: RefCell<DefIdMap<ast::NodeId>>,
pub extern_const_fns: RefCell<DefIdMap<ast::NodeId>>,
pub method_map: MethodMap<'tcx>,
@ -2808,6 +2808,7 @@ pub fn mk_ctxt<'tcx>(s: Session,
upvar_capture_map: RefCell::new(FnvHashMap()),
extern_const_statics: RefCell::new(DefIdMap()),
extern_const_variants: RefCell::new(DefIdMap()),
extern_const_fns: RefCell::new(DefIdMap()),
method_map: RefCell::new(FnvHashMap()),
dependency_formats: RefCell::new(FnvHashMap()),
closure_kinds: RefCell::new(DefIdMap()),

View file

@ -96,20 +96,10 @@ impl<'a> Code<'a> {
/// Attempts to construct a Code from presumed FnLike or Block node input.
pub fn from_node(node: Node) -> Option<Code> {
fn new(node: Node) -> FnLikeNode { FnLikeNode { node: node } }
match node {
ast_map::NodeItem(item) if item.is_fn_like() =>
Some(FnLikeCode(new(node))),
ast_map::NodeTraitItem(tm) if tm.is_fn_like() =>
Some(FnLikeCode(new(node))),
ast_map::NodeImplItem(_) =>
Some(FnLikeCode(new(node))),
ast_map::NodeExpr(e) if e.is_fn_like() =>
Some(FnLikeCode(new(node))),
ast_map::NodeBlock(block) =>
Some(BlockCode(block)),
_ =>
None,
if let ast_map::NodeBlock(block) = node {
Some(BlockCode(block))
} else {
FnLikeNode::from_node(node).map(|fn_like| FnLikeCode(fn_like))
}
}
}
@ -145,6 +135,24 @@ impl<'a> ClosureParts<'a> {
}
impl<'a> FnLikeNode<'a> {
/// Attempts to construct a FnLikeNode from presumed FnLike node input.
pub fn from_node(node: Node) -> Option<FnLikeNode> {
let fn_like = match node {
ast_map::NodeItem(item) => item.is_fn_like(),
ast_map::NodeTraitItem(tm) => tm.is_fn_like(),
ast_map::NodeImplItem(_) => true,
ast_map::NodeExpr(e) => e.is_fn_like(),
_ => false
};
if fn_like {
Some(FnLikeNode {
node: node
})
} else {
None
}
}
pub fn to_fn_parts(self) -> FnParts<'a> {
FnParts {
decl: self.decl(),