diff --git a/src/librustc/metadata/common.rs b/src/librustc/metadata/common.rs index 478c0f2f564f8..901afc1d1904e 100644 --- a/src/librustc/metadata/common.rs +++ b/src/librustc/metadata/common.rs @@ -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; diff --git a/src/librustc/metadata/csearch.rs b/src/librustc/metadata/csearch.rs index 8a35c01200490..f834076e8eab5 100644 --- a/src/librustc/metadata/csearch.rs +++ b/src/librustc/metadata/csearch.rs @@ -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 { diff --git a/src/librustc/metadata/decoder.rs b/src/librustc/metadata/decoder.rs index 2c11ee8baa9d6..ee5fd0202d69e 100644 --- a/src/librustc/metadata/decoder.rs +++ b/src/librustc/metadata/decoder.rs @@ -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 { 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, diff --git a/src/librustc/metadata/encoder.rs b/src/librustc/metadata/encoder.rs index 744b743a337a9..f721bb700ed02 100644 --- a/src/librustc/metadata/encoder.rs +++ b/src/librustc/metadata/encoder.rs @@ -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); diff --git a/src/librustc/middle/check_const.rs b/src/librustc/middle/check_const.rs index 794cc4ff38d34..aca2e2b473e4e 100644 --- a/src/librustc/middle/check_const.rs +++ b/src/librustc/middle/check_const.rs @@ -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(_) | diff --git a/src/librustc/middle/const_eval.rs b/src/librustc/middle/const_eval.rs index 03de553e648d8..8daf17cb0033e 100644 --- a/src/librustc/middle/const_eval.rs +++ b/src/librustc/middle/const_eval.rs @@ -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 { + 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> { + + 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), diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs index 2765fd638600a..5ff6ee3c8b081 100644 --- a/src/librustc/middle/ty.rs +++ b/src/librustc/middle/ty.rs @@ -775,10 +775,10 @@ pub struct ctxt<'tcx> { /// Borrows pub upvar_capture_map: RefCell, - /// 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>, pub extern_const_variants: RefCell>, + pub extern_const_fns: RefCell>, 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()), diff --git a/src/libsyntax/ast_map/blocks.rs b/src/libsyntax/ast_map/blocks.rs index 8b2b2fbeca140..58627b37a8766 100644 --- a/src/libsyntax/ast_map/blocks.rs +++ b/src/libsyntax/ast_map/blocks.rs @@ -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 { - 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 { + 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(),