提交 e9818564 编写于 作者: B bors

Merge pull request #20295 from eddyb/poly-const

Allow paths in constants to refer to polymorphic items.

Reviewed-by: nikomatsakis
......@@ -14,9 +14,7 @@
use util::ppaux;
use syntax::ast;
use syntax::ast_util;
use syntax::visit::Visitor;
use syntax::visit;
use syntax::visit::{self, Visitor};
struct CheckCrateVisitor<'a, 'tcx: 'a> {
tcx: &'a ty::ctxt<'tcx>,
......@@ -37,24 +35,39 @@ fn inside_const<F>(&mut self, f: F) where
{
self.with_const(true, f);
}
fn outside_const<F>(&mut self, f: F) where
F: FnOnce(&mut CheckCrateVisitor<'a, 'tcx>),
{
self.with_const(false, f);
}
}
impl<'a, 'tcx, 'v> Visitor<'v> for CheckCrateVisitor<'a, 'tcx> {
fn visit_item(&mut self, i: &ast::Item) {
check_item(self, i);
match i.node {
ast::ItemStatic(_, _, ref ex) |
ast::ItemConst(_, ref ex) => {
self.inside_const(|v| v.visit_expr(&**ex));
}
ast::ItemEnum(ref enum_definition, _) => {
self.inside_const(|v| {
for var in enum_definition.variants.iter() {
if let Some(ref ex) = var.node.disr_expr {
v.visit_expr(&**ex);
}
}
});
}
_ => self.with_const(false, |v| visit::walk_item(v, i))
}
}
fn visit_pat(&mut self, p: &ast::Pat) {
check_pat(self, p);
let is_const = match p.node {
ast::PatLit(_) | ast::PatRange(..) => true,
_ => false
};
self.with_const(is_const, |v| visit::walk_pat(v, p))
}
fn visit_expr(&mut self, ex: &ast::Expr) {
if check_expr(self, ex) {
visit::walk_expr(self, ex);
if self.in_const {
check_expr(self, ex);
}
visit::walk_expr(self, ex);
}
}
......@@ -64,57 +77,13 @@ pub fn check_crate(tcx: &ty::ctxt) {
tcx.sess.abort_if_errors();
}
fn check_item(v: &mut CheckCrateVisitor, it: &ast::Item) {
match it.node {
ast::ItemStatic(_, _, ref ex) |
ast::ItemConst(_, ref ex) => {
v.inside_const(|v| v.visit_expr(&**ex));
}
ast::ItemEnum(ref enum_definition, _) => {
for var in (*enum_definition).variants.iter() {
for ex in var.node.disr_expr.iter() {
v.inside_const(|v| v.visit_expr(&**ex));
}
}
}
_ => v.outside_const(|v| visit::walk_item(v, it))
}
}
fn check_pat(v: &mut CheckCrateVisitor, p: &ast::Pat) {
fn is_str(e: &ast::Expr) -> bool {
match e.node {
ast::ExprBox(_, ref expr) => {
match expr.node {
ast::ExprLit(ref lit) => ast_util::lit_is_str(&**lit),
_ => false,
}
}
_ => false,
}
}
match p.node {
// Let through plain ~-string literals here
ast::PatLit(ref a) => if !is_str(&**a) { v.inside_const(|v| v.visit_expr(&**a)); },
ast::PatRange(ref a, ref b) => {
if !is_str(&**a) { v.inside_const(|v| v.visit_expr(&**a)); }
if !is_str(&**b) { v.inside_const(|v| v.visit_expr(&**b)); }
}
_ => v.outside_const(|v| visit::walk_pat(v, p))
}
}
fn check_expr(v: &mut CheckCrateVisitor, e: &ast::Expr) -> bool {
if !v.in_const { return true }
fn check_expr(v: &mut CheckCrateVisitor, e: &ast::Expr) {
match e.node {
ast::ExprUnary(ast::UnDeref, _) => {}
ast::ExprUnary(ast::UnUniq, _) => {
span_err!(v.tcx.sess, e.span, E0010,
"cannot do allocations in constant expressions");
return false;
}
ast::ExprLit(ref lit) if ast_util::lit_is_str(&**lit) => {}
ast::ExprBinary(..) | ast::ExprUnary(..) => {
let method_call = ty::MethodCall::expr(e.id);
if v.tcx.method_map.borrow().contains_key(&method_call) {
......@@ -123,7 +92,7 @@ fn check_expr(v: &mut CheckCrateVisitor, e: &ast::Expr) -> bool {
expressions");
}
}
ast::ExprLit(_) => (),
ast::ExprLit(_) => {}
ast::ExprCast(ref from, _) => {
let toty = ty::expr_ty(v.tcx, e);
let fromty = ty::expr_ty(v.tcx, &**from);
......@@ -142,39 +111,23 @@ fn check_expr(v: &mut CheckCrateVisitor, e: &ast::Expr) -> bool {
expression");
}
}
ast::ExprPath(ref pth) => {
// NB: In the future you might wish to relax this slightly
// to handle on-demand instantiation of functions via
// foo::<bar> in a const. Currently that is only done on
// a path in trans::callee that only works in block contexts.
if !pth.segments.iter().all(|segment| segment.parameters.is_empty()) {
span_err!(v.tcx.sess, e.span, E0013,
"paths in constants may only refer to items without \
type parameters");
}
match v.tcx.def_map.borrow().get(&e.id) {
Some(&DefStatic(..)) |
Some(&DefConst(..)) |
Some(&DefFn(..)) |
Some(&DefVariant(_, _, _)) |
Some(&DefStruct(_)) => { }
ast::ExprPath(_) => {
match v.tcx.def_map.borrow()[e.id] {
DefStatic(..) | DefConst(..) |
DefFn(..) | DefStaticMethod(..) | DefMethod(..) |
DefStruct(_) | DefVariant(_, _, _) => {}
Some(&def) => {
def => {
debug!("(checking const) found bad def: {}", def);
span_err!(v.tcx.sess, e.span, E0014,
"paths in constants may only refer to constants \
or functions");
}
None => {
v.tcx.sess.span_bug(e.span, "unbound path in const?!");
}
}
}
ast::ExprCall(ref callee, _) => {
match v.tcx.def_map.borrow().get(&callee.id) {
Some(&DefStruct(..)) |
Some(&DefVariant(..)) => {} // OK.
match v.tcx.def_map.borrow()[callee.id] {
DefStruct(..) | DefVariant(..) => {} // OK.
_ => {
span_err!(v.tcx.sess, e.span, E0015,
"function calls in constants are limited to \
......@@ -190,9 +143,9 @@ fn check_expr(v: &mut CheckCrateVisitor, e: &ast::Expr) -> bool {
"blocks in constants are limited to items and \
tail expressions");
match stmt.node {
ast::StmtDecl(ref span, _) => {
match span.node {
ast::DeclLocal(_) => block_span_err(span.span),
ast::StmtDecl(ref decl, _) => {
match decl.node {
ast::DeclLocal(_) => block_span_err(decl.span),
// Item statements are allowed
ast::DeclItem(_) => {}
......@@ -206,10 +159,6 @@ fn check_expr(v: &mut CheckCrateVisitor, e: &ast::Expr) -> bool {
}
}
}
match block.expr {
Some(ref expr) => { check_expr(v, &**expr); }
None => {}
}
}
ast::ExprVec(_) |
ast::ExprAddrOf(ast::MutImmutable, _) |
......@@ -232,11 +181,7 @@ fn check_expr(v: &mut CheckCrateVisitor, e: &ast::Expr) -> bool {
}
}
_ => {
span_err!(v.tcx.sess, e.span, E0019,
"constant contains unimplemented expression type");
return false;
}
_ => span_err!(v.tcx.sess, e.span, E0019,
"constant contains unimplemented expression type")
}
true
}
......@@ -559,14 +559,14 @@ pub fn cat_def(&self,
id, expr_ty.repr(self.tcx()), def);
match def {
def::DefStruct(..) | def::DefVariant(..) | def::DefFn(..) |
def::DefStaticMethod(..) | def::DefConst(..) => {
def::DefStruct(..) | def::DefVariant(..) | def::DefConst(..) |
def::DefFn(..) | def::DefStaticMethod(..) | def::DefMethod(..) => {
Ok(self.cat_rvalue_node(id, span, expr_ty))
}
def::DefMod(_) | def::DefForeignMod(_) | def::DefUse(_) |
def::DefTrait(_) | def::DefTy(..) | def::DefPrimTy(_) |
def::DefTyParam(..) | def::DefTyParamBinder(..) | def::DefRegion(_) |
def::DefLabel(_) | def::DefSelfTy(..) | def::DefMethod(..) |
def::DefLabel(_) | def::DefSelfTy(..) |
def::DefAssociatedTy(..) | def::DefAssociatedPath(..)=> {
Ok(Rc::new(cmt_ {
id:id,
......
......@@ -511,7 +511,7 @@ pub fn get_res_dtor<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
// Since we're in trans we don't care for any region parameters
let substs = subst::Substs::erased(substs.types.clone());
let (val, _) = monomorphize::monomorphic_fn(ccx, did, &substs, None);
let (val, _, _) = monomorphize::monomorphic_fn(ccx, did, &substs, None);
val
} else if did.krate == ast::LOCAL_CRATE {
......
......@@ -38,6 +38,7 @@
use trans::closure;
use trans::common;
use trans::common::*;
use trans::consts;
use trans::datum::*;
use trans::expr;
use trans::glue;
......@@ -152,7 +153,8 @@ fn trans_def<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
_ => false
}
} => {
let substs = node_id_substs(bcx, ExprId(ref_expr.id));
let substs = node_id_substs(bcx.ccx(), ExprId(ref_expr.id),
bcx.fcx.param_substs);
Callee {
bcx: bcx,
data: NamedTupleConstructor(substs, 0)
......@@ -162,23 +164,28 @@ fn trans_def<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
ty::ty_bare_fn(_, ref f) => f.abi == synabi::RustIntrinsic,
_ => false
} => {
let substs = node_id_substs(bcx, ExprId(ref_expr.id));
let substs = node_id_substs(bcx.ccx(), ExprId(ref_expr.id),
bcx.fcx.param_substs);
let def_id = inline::maybe_instantiate_inline(bcx.ccx(), did);
Callee { bcx: bcx, data: Intrinsic(def_id.node, substs) }
}
def::DefFn(did, _) | def::DefMethod(did, _, def::FromImpl(_)) |
def::DefStaticMethod(did, def::FromImpl(_)) => {
fn_callee(bcx, trans_fn_ref(bcx, did, ExprId(ref_expr.id)))
fn_callee(bcx, trans_fn_ref(bcx.ccx(), did, ExprId(ref_expr.id),
bcx.fcx.param_substs).val)
}
def::DefStaticMethod(meth_did, def::FromTrait(trait_did)) |
def::DefMethod(meth_did, _, def::FromTrait(trait_did)) => {
fn_callee(bcx, meth::trans_static_method_callee(bcx, meth_did,
fn_callee(bcx, meth::trans_static_method_callee(bcx.ccx(),
meth_did,
trait_did,
ref_expr.id))
ref_expr.id,
bcx.fcx.param_substs).val)
}
def::DefVariant(tid, vid, _) => {
let vinfo = ty::enum_variant_with_id(bcx.tcx(), tid, vid);
let substs = node_id_substs(bcx, ExprId(ref_expr.id));
let substs = node_id_substs(bcx.ccx(), ExprId(ref_expr.id),
bcx.fcx.param_substs);
// Nullary variants are not callable
assert!(vinfo.args.len() > 0u);
......@@ -189,7 +196,8 @@ fn trans_def<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
}
}
def::DefStruct(_) => {
let substs = node_id_substs(bcx, ExprId(ref_expr.id));
let substs = node_id_substs(bcx.ccx(), ExprId(ref_expr.id),
bcx.fcx.param_substs);
Callee {
bcx: bcx,
data: NamedTupleConstructor(substs, 0)
......@@ -217,15 +225,19 @@ fn trans_def<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
/// Translates a reference (with id `ref_id`) to the fn/method with id `def_id` into a function
/// pointer. This may require monomorphization or inlining.
pub fn trans_fn_ref(bcx: Block, def_id: ast::DefId, node: ExprOrMethodCall) -> ValueRef {
pub fn trans_fn_ref<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
def_id: ast::DefId,
node: ExprOrMethodCall,
param_substs: &subst::Substs<'tcx>)
-> Datum<'tcx, Rvalue> {
let _icx = push_ctxt("trans_fn_ref");
let substs = node_id_substs(bcx, node);
let substs = node_id_substs(ccx, node, param_substs);
debug!("trans_fn_ref(def_id={}, node={}, substs={})",
def_id.repr(bcx.tcx()),
def_id.repr(ccx.tcx()),
node,
substs.repr(bcx.tcx()));
trans_fn_ref_with_substs(bcx, def_id, node, substs)
substs.repr(ccx.tcx()));
trans_fn_ref_with_substs(ccx, def_id, node, param_substs, substs)
}
fn trans_fn_ref_with_substs_to_callee<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
......@@ -235,10 +247,11 @@ fn trans_fn_ref_with_substs_to_callee<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
-> Callee<'blk, 'tcx> {
Callee {
bcx: bcx,
data: Fn(trans_fn_ref_with_substs(bcx,
data: Fn(trans_fn_ref_with_substs(bcx.ccx(),
def_id,
ExprId(ref_id),
substs)),
bcx.fcx.param_substs,
substs).val),
}
}
......@@ -364,28 +377,30 @@ pub fn trans_fn_pointer_shim<'a, 'tcx>(
///
/// # Parameters
///
/// - `bcx`: the current block where the reference to the fn occurs
/// - `ccx`: the crate context
/// - `def_id`: def id of the fn or method item being referenced
/// - `node`: node id of the reference to the fn/method, if applicable.
/// This parameter may be zero; but, if so, the resulting value may not
/// have the right type, so it must be cast before being used.
/// - `param_substs`: if the `node` is in a polymorphic function, these
/// are the substitutions required to monomorphize its type
/// - `substs`: values for each of the fn/method's parameters
pub fn trans_fn_ref_with_substs<'blk, 'tcx>(
bcx: Block<'blk, 'tcx>, //
def_id: ast::DefId, // def id of fn
node: ExprOrMethodCall, // node id of use of fn; may be zero if N/A
substs: subst::Substs<'tcx>) // vtables for the call
-> ValueRef
pub fn trans_fn_ref_with_substs<'a, 'tcx>(
ccx: &CrateContext<'a, 'tcx>,
def_id: ast::DefId,
node: ExprOrMethodCall,
param_substs: &subst::Substs<'tcx>,
substs: subst::Substs<'tcx>)
-> Datum<'tcx, Rvalue>
{
let _icx = push_ctxt("trans_fn_ref_with_substs");
let ccx = bcx.ccx();
let tcx = bcx.tcx();
let tcx = ccx.tcx();
debug!("trans_fn_ref_with_substs(bcx={}, def_id={}, node={}, \
substs={})",
bcx.to_str(),
debug!("trans_fn_ref_with_substs(def_id={}, node={}, \
param_substs={}, substs={})",
def_id.repr(tcx),
node,
param_substs.repr(tcx),
substs.repr(tcx));
assert!(substs.types.all(|t| !ty::type_needs_infer(*t)));
......@@ -443,15 +458,15 @@ pub fn trans_fn_ref_with_substs<'blk, 'tcx>(
(true, source_id, new_substs)
}
ty::TypeTraitItem(_) => {
bcx.tcx().sess.bug("trans_fn_ref_with_vtables() tried \
to translate an associated type?!")
tcx.sess.bug("trans_fn_ref_with_vtables() tried \
to translate an associated type?!")
}
}
}
};
// If this is an unboxed closure, redirect to it.
match closure::get_or_create_declaration_if_unboxed_closure(bcx,
match closure::get_or_create_declaration_if_unboxed_closure(ccx,
def_id,
&substs) {
None => {}
......@@ -494,24 +509,27 @@ pub fn trans_fn_ref_with_substs<'blk, 'tcx>(
MethodCallKey(_) => None,
};
let (val, must_cast) =
let (val, fn_ty, must_cast) =
monomorphize::monomorphic_fn(ccx, def_id, &substs, opt_ref_id);
let mut val = val;
if must_cast && node != ExprId(0) {
// Monotype of the REFERENCE to the function (type params
// are subst'd)
let ref_ty = match node {
ExprId(id) => node_id_type(bcx, id),
ExprId(id) => ty::node_id_to_type(tcx, id),
MethodCallKey(method_call) => {
let t = (*bcx.tcx().method_map.borrow())[method_call].ty;
monomorphize_type(bcx, t)
(*tcx.method_map.borrow())[method_call].ty
}
};
val = PointerCast(
bcx, val, type_of::type_of_fn_from_ty(ccx, ref_ty).ptr_to());
let ref_ty = monomorphize::apply_param_substs(tcx,
param_substs,
&ref_ty);
let llptrty = type_of::type_of_fn_from_ty(ccx, ref_ty).ptr_to();
if llptrty != val_ty(val) {
let val = consts::ptrcast(val, llptrty);
return Datum::new(val, ref_ty, Rvalue::new(ByValue));
}
}
return val;
return Datum::new(val, fn_ty, Rvalue::new(ByValue));
}
// Type scheme of the function item (may have type params)
......@@ -556,12 +574,12 @@ pub fn trans_fn_ref_with_substs<'blk, 'tcx>(
let llptrty = llty.ptr_to();
if val_ty(val) != llptrty {
debug!("trans_fn_ref_with_vtables(): casting pointer!");
val = BitCast(bcx, val, llptrty);
val = consts::ptrcast(val, llptrty);
} else {
debug!("trans_fn_ref_with_vtables(): not casting pointer!");
}
val
Datum::new(val, fn_type, Rvalue::new(ByValue))
}
// ______________________________________________________________________
......
......@@ -726,7 +726,10 @@ fn get_or_create_landing_pad(&'blk self) -> BasicBlockRef {
// specify any of the types for the function, we just make it a symbol
// that LLVM can later use.
let llpersonality = match pad_bcx.tcx().lang_items.eh_personality() {
Some(def_id) => callee::trans_fn_ref(pad_bcx, def_id, ExprId(0)),
Some(def_id) => {
callee::trans_fn_ref(pad_bcx.ccx(), def_id, ExprId(0),
pad_bcx.fcx.param_substs).val
}
None => {
let mut personality = self.ccx.eh_personality().borrow_mut();
match *personality {
......
......@@ -20,6 +20,7 @@
use trans::cleanup::{CleanupMethods, ScopeId};
use trans::common::*;
use trans::datum::{Datum, DatumBlock, Expr, Lvalue, rvalue_scratch_datum};
use trans::datum::{Rvalue, ByValue};
use trans::debuginfo;
use trans::expr;
use trans::monomorphize::{self, MonoId};
......@@ -453,22 +454,21 @@ pub fn trans_expr_fn<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
/// Returns the LLVM function declaration for an unboxed closure, creating it
/// if necessary. If the ID does not correspond to a closure ID, returns None.
pub fn get_or_create_declaration_if_unboxed_closure<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
closure_id: ast::DefId,
substs: &Substs<'tcx>)
-> Option<ValueRef> {
let ccx = bcx.ccx();
pub fn get_or_create_declaration_if_unboxed_closure<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
closure_id: ast::DefId,
substs: &Substs<'tcx>)
-> Option<Datum<'tcx, Rvalue>> {
if !ccx.tcx().unboxed_closures.borrow().contains_key(&closure_id) {
// Not an unboxed closure.
return None
}
let function_type = ty::node_id_to_type(bcx.tcx(), closure_id.node);
let function_type = monomorphize::apply_param_substs(bcx.tcx(), substs, &function_type);
let function_type = ty::node_id_to_type(ccx.tcx(), closure_id.node);
let function_type = monomorphize::apply_param_substs(ccx.tcx(), substs, &function_type);
// Normalize type so differences in regions and typedefs don't cause
// duplicate declarations
let function_type = ty::normalize_ty(bcx.tcx(), function_type);
let function_type = ty::normalize_ty(ccx.tcx(), function_type);
let params = match function_type.sty {
ty::ty_unboxed_closure(_, _, ref substs) => substs.types.clone(),
_ => unreachable!()
......@@ -479,10 +479,10 @@ pub fn get_or_create_declaration_if_unboxed_closure<'blk, 'tcx>(bcx: Block<'blk,
};
match ccx.unboxed_closure_vals().borrow().get(&mono_id) {
Some(llfn) => {
Some(&llfn) => {
debug!("get_or_create_declaration_if_unboxed_closure(): found \
closure");
return Some(*llfn)
return Some(Datum::new(llfn, function_type, Rvalue::new(ByValue)))
}
None => {}
}
......@@ -502,7 +502,7 @@ pub fn get_or_create_declaration_if_unboxed_closure<'blk, 'tcx>(bcx: Block<'blk,
ccx.tn().type_to_string(val_ty(llfn)));
ccx.unboxed_closure_vals().borrow_mut().insert(mono_id, llfn);
Some(llfn)
Some(Datum::new(llfn, function_type, Rvalue::new(ByValue)))
}
pub fn trans_unboxed_closure<'blk, 'tcx>(
......@@ -519,7 +519,7 @@ pub fn trans_unboxed_closure<'blk, 'tcx>(
let closure_id = ast_util::local_def(id);
let llfn = get_or_create_declaration_if_unboxed_closure(
bcx,
bcx.ccx(),
closure_id,
bcx.fcx.param_substs).unwrap();
......@@ -539,7 +539,7 @@ pub fn trans_unboxed_closure<'blk, 'tcx>(
trans_closure(bcx.ccx(),
decl,
body,
llfn,
llfn.val,
bcx.fcx.param_substs,
id,
&[],
......
......@@ -28,6 +28,7 @@
use trans::base;
use trans::build;
use trans::cleanup;
use trans::consts;
use trans::datum;
use trans::debuginfo;
use trans::machine;
......@@ -803,12 +804,9 @@ pub fn C_cstr(cx: &CrateContext, s: InternedString, null_terminated: bool) -> Va
// NB: Do not use `do_spill_noroot` to make this into a constant string, or
// you will be kicked off fast isel. See issue #4352 for an example of this.
pub fn C_str_slice(cx: &CrateContext, s: InternedString) -> ValueRef {
unsafe {
let len = s.get().len();
let cs = llvm::LLVMConstPointerCast(C_cstr(cx, s, false),
Type::i8p(cx).to_ref());
C_named_struct(cx.tn().find_type("str_slice").unwrap(), &[cs, C_uint(cx, len)])
}
let len = s.get().len();
let cs = consts::ptrcast(C_cstr(cx, s, false), Type::i8p(cx));
C_named_struct(cx.tn().find_type("str_slice").unwrap(), &[cs, C_uint(cx, len)])
}
pub fn C_binary_slice(cx: &CrateContext, data: &[u8]) -> ValueRef {
......@@ -824,7 +822,7 @@ pub fn C_binary_slice(cx: &CrateContext, data: &[u8]) -> ValueRef {
llvm::LLVMSetGlobalConstant(g, True);
llvm::SetLinkage(g, llvm::InternalLinkage);
let cs = llvm::LLVMConstPointerCast(g, Type::i8p(cx).to_ref());
let cs = consts::ptrcast(g, Type::i8p(cx));
C_struct(cx, &[cs, C_uint(cx, len)], false)
}
}
......@@ -1095,11 +1093,11 @@ pub enum ExprOrMethodCall {
MethodCallKey(ty::MethodCall)
}
pub fn node_id_substs<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
node: ExprOrMethodCall)
-> subst::Substs<'tcx>
{
let tcx = bcx.tcx();
pub fn node_id_substs<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
node: ExprOrMethodCall,
param_substs: &subst::Substs<'tcx>)
-> subst::Substs<'tcx> {
let tcx = ccx.tcx();
let substs = match node {
ExprId(id) => {
......@@ -1111,15 +1109,13 @@ pub fn node_id_substs<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
};
if substs.types.any(|t| ty::type_needs_infer(*t)) {
bcx.sess().bug(
format!("type parameters for node {} include inference types: \
{}",
node,
substs.repr(bcx.tcx()))[]);
tcx.sess.bug(format!("type parameters for node {} include inference types: {}",
node, substs.repr(tcx))[]);
}
let substs = substs.erase_regions();
bcx.monomorphize(&substs)
monomorphize::apply_param_substs(tcx,
param_substs,
&substs.erase_regions())
}
pub fn langcall(bcx: Block,
......
......@@ -14,13 +14,13 @@
use llvm::{ConstFCmp, ConstICmp, SetLinkage, PrivateLinkage, ValueRef, Bool, True, False};
use llvm::{IntEQ, IntNE, IntUGT, IntUGE, IntULT, IntULE, IntSGT, IntSGE, IntSLT, IntSLE,
RealOEQ, RealOGT, RealOGE, RealOLT, RealOLE, RealONE};
use metadata::csearch;
use middle::{const_eval, def};
use trans::{adt, closure, consts, debuginfo, expr, inline, machine};
use trans::base::{self, push_ctxt};
use trans::common::*;
use trans::type_::Type;
use trans::type_of;
use middle::subst::Substs;
use middle::ty::{self, Ty};
use util::ppaux::{Repr, ty_to_string};
......@@ -79,11 +79,9 @@ pub fn const_lit(cx: &CrateContext, e: &ast::Expr, lit: &ast::Lit)
}
}
pub fn const_ptrcast(cx: &CrateContext, a: ValueRef, t: Type) -> ValueRef {
pub fn ptrcast(val: ValueRef, ty: Type) -> ValueRef {
unsafe {
let b = llvm::LLVMConstPointerCast(a, t.ptr_to().to_ref());
assert!(cx.const_globals().borrow_mut().insert(b as int, a).is_none());
b
llvm::LLVMConstPointerCast(val, ty.to_ref())
}
}
......@@ -258,7 +256,9 @@ pub fn const_expr<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, e: &ast::Expr)
match ty.sty {
ty::ty_vec(unit_ty, Some(len)) => {
let llunitty = type_of::type_of(cx, unit_ty);
let llptr = const_ptrcast(cx, llconst, llunitty);
let llptr = ptrcast(llconst, llunitty.ptr_to());
assert!(cx.const_globals().borrow_mut()
.insert(llptr as int, llconst).is_none());
assert_eq!(abi::FAT_PTR_ADDR, 0);
assert_eq!(abi::FAT_PTR_EXTRA, 1);
llconst = C_struct(cx, &[
......@@ -523,7 +523,7 @@ fn const_expr_unadjusted(cx: &CrateContext, e: &ast::Expr) -> ValueRef {
}
}
(expr::cast_pointer, expr::cast_pointer) => {
llvm::LLVMConstPointerCast(v, llty.to_ref())
ptrcast(v, llty)
}
(expr::cast_integral, expr::cast_pointer) => {
llvm::LLVMConstIntToPtr(v, llty.to_ref())
......@@ -616,36 +616,38 @@ fn const_expr_unadjusted(cx: &CrateContext, e: &ast::Expr) -> ValueRef {
C_array(llunitty, vs[])
}
}
ast::ExprPath(ref pth) => {
// Assert that there are no type parameters in this path.
assert!(pth.segments.iter().all(|seg| !seg.parameters.has_types()));
let opt_def = cx.tcx().def_map.borrow().get(&e.id).cloned();
match opt_def {
Some(def::DefFn(def_id, _)) => {
if !ast_util::is_local(def_id) {
let ty = csearch::get_type(cx.tcx(), def_id).ty;
base::trans_external_path(cx, def_id, ty)
} else {
assert!(ast_util::is_local(def_id));
base::get_item_val(cx, def_id.node)
}
ast::ExprPath(_) => {
let def = cx.tcx().def_map.borrow()[e.id];
match def {
def::DefFn(..) | def::DefStaticMethod(..) | def::DefMethod(..) => {
expr::trans_def_fn_unadjusted(cx, e, def, &Substs::trans_empty()).val
}
Some(def::DefConst(def_id)) => {
def::DefConst(def_id) => {
get_const_val(cx, def_id)
}
Some(def::DefVariant(enum_did, variant_did, _)) => {
let ety = ty::expr_ty(cx.tcx(), e);
let repr = adt::represent_type(cx, ety);
def::DefVariant(enum_did, variant_did, _) => {
let vinfo = ty::enum_variant_with_id(cx.tcx(),
enum_did,
variant_did);
adt::trans_const(cx, &*repr, vinfo.disr_val, &[])
if vinfo.args.len() > 0 {
// N-ary variant.
expr::trans_def_fn_unadjusted(cx, e, def, &Substs::trans_empty()).val
} else {
// Nullary variant.
let ety = ty::expr_ty(cx.tcx(), e);
let repr = adt::represent_type(cx, ety);
adt::trans_const(cx, &*repr, vinfo.disr_val, &[])
}
}
Some(def::DefStruct(_)) => {
def::DefStruct(_) => {
let ety = ty::expr_ty(cx.tcx(), e);
let llty = type_of::type_of(cx, ety);
C_null(llty)
if let ty::ty_bare_fn(..) = ety.sty {
// Tuple struct.
expr::trans_def_fn_unadjusted(cx, e, def, &Substs::trans_empty()).val
} else {
// Unit struct.
C_null(type_of::type_of(cx, ety))
}
}
_ => {
cx.sess().span_bug(e.span, "expected a const, fn, struct, \
......
......@@ -853,7 +853,9 @@ fn trans_def<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
match def {
def::DefFn(..) | def::DefStaticMethod(..) | def::DefMethod(..) |
def::DefStruct(_) | def::DefVariant(..) => {
trans_def_fn_unadjusted(bcx, ref_expr, def)
let datum = trans_def_fn_unadjusted(bcx.ccx(), ref_expr, def,
bcx.fcx.param_substs);
DatumBlock::new(bcx, datum.to_expr_datum())
}
def::DefStatic(did, _) => {
// There are two things that may happen here:
......@@ -1250,7 +1252,9 @@ fn trans_def_dps_unadjusted<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
let variant_info = ty::enum_variant_with_id(bcx.tcx(), tid, vid);
if variant_info.args.len() > 0u {
// N-ary variant.
let llfn = callee::trans_fn_ref(bcx, vid, ExprId(ref_expr.id));
let llfn = callee::trans_fn_ref(bcx.ccx(), vid,
ExprId(ref_expr.id),
bcx.fcx.param_substs).val;
Store(bcx, llfn, lldest);
return bcx;
} else {
......@@ -1281,34 +1285,33 @@ fn trans_def_dps_unadjusted<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
}
}
fn trans_def_fn_unadjusted<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
ref_expr: &ast::Expr,
def: def::Def)
-> DatumBlock<'blk, 'tcx, Expr> {
pub fn trans_def_fn_unadjusted<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
ref_expr: &ast::Expr,
def: def::Def,
param_substs: &subst::Substs<'tcx>)
-> Datum<'tcx, Rvalue> {
let _icx = push_ctxt("trans_def_datum_unadjusted");
let llfn = match def {
match def {
def::DefFn(did, _) |
def::DefStruct(did) | def::DefVariant(_, did, _) |
def::DefStaticMethod(did, def::FromImpl(_)) |
def::DefMethod(did, _, def::FromImpl(_)) => {
callee::trans_fn_ref(bcx, did, ExprId(ref_expr.id))
callee::trans_fn_ref(ccx, did, ExprId(ref_expr.id), param_substs)
}
def::DefStaticMethod(impl_did, def::FromTrait(trait_did)) |
def::DefMethod(impl_did, _, def::FromTrait(trait_did)) => {
meth::trans_static_method_callee(bcx, impl_did,
trait_did, ref_expr.id)
meth::trans_static_method_callee(ccx, impl_did,
trait_did, ref_expr.id,
param_substs)
}
_ => {
bcx.tcx().sess.span_bug(ref_expr.span, format!(
ccx.tcx().sess.span_bug(ref_expr.span, format!(
"trans_def_fn_unadjusted invoked on: {} for {}",
def,
ref_expr.repr(bcx.tcx()))[]);
ref_expr.repr(ccx.tcx()))[]);
}
};
let fn_ty = expr_ty(bcx, ref_expr);
DatumBlock::new(bcx, Datum::new(llfn, fn_ty, RvalueExpr(Rvalue::new(ByValue))))
}
}
/// Translates a reference to a local variable or argument. This always results in an lvalue datum.
......
......@@ -26,6 +26,7 @@
use trans::callee;
use trans::cleanup;
use trans::cleanup::CleanupMethods;
use trans::consts;
use trans::common::*;
use trans::datum;
use trans::debuginfo;
......@@ -577,9 +578,7 @@ pub fn emit_tydescs(ccx: &CrateContext) {
// before being put into the tydesc because we only have a singleton
// tydesc type. Then we'll recast each function to its real type when
// calling it.
let drop_glue = unsafe {
llvm::LLVMConstPointerCast(get_drop_glue(ccx, ti.ty), glue_fn_ty.to_ref())
};
let drop_glue = consts::ptrcast(get_drop_glue(ccx, ti.ty), glue_fn_ty);
ccx.stats().n_real_glues.set(ccx.stats().n_real_glues.get() + 1);
let tydesc = C_named_struct(ccx.tydesc_type(),
......
......@@ -122,9 +122,10 @@ pub fn trans_method_callee<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
ty::MethodStaticUnboxedClosure(did) => {
Callee {
bcx: bcx,
data: Fn(callee::trans_fn_ref(bcx,
data: Fn(callee::trans_fn_ref(bcx.ccx(),
did,
MethodCallKey(method_call))),
MethodCallKey(method_call),
bcx.fcx.param_substs).val),
}
}
......@@ -166,30 +167,31 @@ pub fn trans_method_callee<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
}
}
pub fn trans_static_method_callee(bcx: Block,
method_id: ast::DefId,
trait_id: ast::DefId,
expr_id: ast::NodeId)
-> ValueRef
pub fn trans_static_method_callee<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
method_id: ast::DefId,
trait_id: ast::DefId,
expr_id: ast::NodeId,
param_substs: &subst::Substs<'tcx>)
-> Datum<'tcx, Rvalue>
{
let _icx = push_ctxt("meth::trans_static_method_callee");
let ccx = bcx.ccx();
let tcx = ccx.tcx();
debug!("trans_static_method_callee(method_id={}, trait_id={}, \
expr_id={})",
method_id,
ty::item_path_str(bcx.tcx(), trait_id),
ty::item_path_str(tcx, trait_id),
expr_id);
let mname = if method_id.krate == ast::LOCAL_CRATE {
match bcx.tcx().map.get(method_id.node) {
match tcx.map.get(method_id.node) {
ast_map::NodeTraitItem(method) => {
let ident = match *method {
ast::RequiredMethod(ref m) => m.ident,
ast::ProvidedMethod(ref m) => m.pe_ident(),
ast::TypeTraitItem(_) => {
bcx.tcx().sess.bug("trans_static_method_callee() on \
an associated type?!")
tcx.sess.bug("trans_static_method_callee() on \
an associated type?!")
}
};
ident.name
......@@ -197,7 +199,7 @@ pub fn trans_static_method_callee(bcx: Block,
_ => panic!("callee is not a trait method")
}
} else {
csearch::get_item_path(bcx.tcx(), method_id).last().unwrap().name()
csearch::get_item_path(tcx, method_id).last().unwrap().name()
};
debug!("trans_static_method_callee: method_id={}, expr_id={}, \
name={}", method_id, expr_id, token::get_name(mname));
......@@ -205,7 +207,7 @@ pub fn trans_static_method_callee(bcx: Block,
// Find the substitutions for the fn itself. This includes
// type parameters that belong to the trait but also some that
// belong to the method:
let rcvr_substs = node_id_substs(bcx, ExprId(expr_id));
let rcvr_substs = node_id_substs(ccx, ExprId(expr_id), param_substs);
let subst::SeparateVecsPerParamSpace {
types: rcvr_type,
selfs: rcvr_self,
......@@ -238,11 +240,11 @@ pub fn trans_static_method_callee(bcx: Block,
Substs::erased(VecPerParamSpace::new(rcvr_type,
rcvr_self,
Vec::new()));
let trait_substs = bcx.tcx().mk_substs(trait_substs);
debug!("trait_substs={}", trait_substs.repr(bcx.tcx()));
let trait_substs = tcx.mk_substs(trait_substs);
debug!("trait_substs={}", trait_substs.repr(tcx));
let trait_ref = ty::Binder(Rc::new(ty::TraitRef { def_id: trait_id,
substs: trait_substs }));
let vtbl = fulfill_obligation(bcx.ccx(),
let vtbl = fulfill_obligation(ccx,
DUMMY_SP,
trait_ref);
......@@ -282,17 +284,13 @@ pub fn trans_static_method_callee(bcx: Block,
rcvr_method));
let mth_id = method_with_name(ccx, impl_did, mname);
let llfn = trans_fn_ref_with_substs(bcx, mth_id, ExprId(expr_id),
callee_substs);
let callee_ty = node_id_type(bcx, expr_id);
let llty = type_of_fn_from_ty(ccx, callee_ty).ptr_to();
PointerCast(bcx, llfn, llty)
trans_fn_ref_with_substs(ccx, mth_id, ExprId(expr_id),
param_substs,
callee_substs)
}
_ => {
bcx.tcx().sess.bug(
format!("static call to invalid vtable: {}",
vtbl.repr(bcx.tcx()))[]);
tcx.sess.bug(format!("static call to invalid vtable: {}",
vtbl.repr(tcx))[]);
}
}
}
......@@ -346,20 +344,22 @@ fn trans_monomorphized_callee<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
bcx, MethodCallKey(method_call), vtable_impl.substs);
// translate the function
let llfn = trans_fn_ref_with_substs(bcx,
let llfn = trans_fn_ref_with_substs(bcx.ccx(),
mth_id,
MethodCallKey(method_call),
callee_substs);
bcx.fcx.param_substs,
callee_substs).val;
Callee { bcx: bcx, data: Fn(llfn) }
}
traits::VtableUnboxedClosure(closure_def_id, substs) => {
// The substitutions should have no type parameters remaining
// after passing through fulfill_obligation
let llfn = trans_fn_ref_with_substs(bcx,
let llfn = trans_fn_ref_with_substs(bcx.ccx(),
closure_def_id,
MethodCallKey(method_call),
substs);
bcx.fcx.param_substs,
substs).val;
Callee {
bcx: bcx,
......@@ -400,7 +400,7 @@ fn combine_impl_and_methods_tps<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
{
let ccx = bcx.ccx();
let node_substs = node_id_substs(bcx, node);
let node_substs = node_id_substs(ccx, node, bcx.fcx.param_substs);
debug!("rcvr_substs={}", rcvr_substs.repr(ccx.tcx()));
debug!("node_substs={}", node_substs.repr(ccx.tcx()));
......@@ -684,10 +684,11 @@ pub fn get_vtable<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
}
traits::VtableUnboxedClosure(closure_def_id, substs) => {
let llfn = trans_fn_ref_with_substs(
bcx,
bcx.ccx(),
closure_def_id,
ExprId(0),
substs.clone());
bcx.fcx.param_substs,
substs.clone()).val;
(vec!(llfn)).into_iter()
}
......@@ -788,10 +789,11 @@ fn emit_vtable_methods<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
Some(C_null(Type::nil(ccx).ptr_to())).into_iter()
} else {
let fn_ref = trans_fn_ref_with_substs(
bcx,
ccx,
m_id,
ExprId(0),
substs.clone());
bcx.fcx.param_substs,
substs.clone()).val;
// currently, at least, by-value self is not object safe
assert!(m.explicit_self != ty::ByValueExplicitSelfCategory);
......
......@@ -38,7 +38,7 @@ pub fn monomorphic_fn<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
fn_id: ast::DefId,
psubsts: &subst::Substs<'tcx>,
ref_id: Option<ast::NodeId>)
-> (ValueRef, bool) {
-> (ValueRef, Ty<'tcx>, bool) {
debug!("monomorphic_fn(\
fn_id={}, \
real_substs={}, \
......@@ -58,11 +58,14 @@ pub fn monomorphic_fn<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
params: psubsts.types.clone()
};
let item_ty = ty::lookup_item_type(ccx.tcx(), fn_id).ty;
let mono_ty = item_ty.subst(ccx.tcx(), psubsts);
match ccx.monomorphized().borrow().get(&hash_id) {
Some(&val) => {
debug!("leaving monomorphic fn {}",
ty::item_path_str(ccx.tcx(), fn_id));
return (val, false);
return (val, mono_ty, false);
}
None => ()
}
......@@ -75,8 +78,6 @@ pub fn monomorphic_fn<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
psubsts.repr(ccx.tcx()),
hash_id);
let tpt = ty::lookup_item_type(ccx.tcx(), fn_id);
let llitem_ty = tpt.ty;
let map_node = session::expect(
ccx.sess(),
......@@ -91,13 +92,12 @@ pub fn monomorphic_fn<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
if let ast_map::NodeForeignItem(_) = map_node {
if ccx.tcx().map.get_foreign_abi(fn_id.node) != abi::RustIntrinsic {
// Foreign externs don't have to be monomorphized.
return (get_item_val(ccx, fn_id.node), true);
return (get_item_val(ccx, fn_id.node), mono_ty, true);
}
}
debug!("monomorphic_fn about to subst into {}", llitem_ty.repr(ccx.tcx()));
debug!("monomorphic_fn about to subst into {}", item_ty.repr(ccx.tcx()));
let mono_ty = llitem_ty.subst(ccx.tcx(), psubsts);
debug!("mono_ty = {} (post-substitution)", mono_ty.repr(ccx.tcx()));
let mono_ty = normalize_associated_type(ccx.tcx(), &mono_ty);
......@@ -283,7 +283,7 @@ pub fn monomorphic_fn<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
ccx.monomorphizing().borrow_mut().insert(fn_id, depth);
debug!("leaving monomorphic fn {}", ty::item_path_str(ccx.tcx(), fn_id));
(lldecl, true)
(lldecl, mono_ty, true)
}
#[derive(PartialEq, Eq, Hash, Show)]
......
......@@ -19,6 +19,7 @@
use trans::cleanup;
use trans::cleanup::CleanupMethods;
use trans::common::*;
use trans::consts;
use trans::datum::*;
use trans::expr::{Dest, Ignore, SaveIn};
use trans::expr;
......@@ -213,15 +214,13 @@ pub fn trans_lit_str<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
match dest {
Ignore => bcx,
SaveIn(lldest) => {
unsafe {
let bytes = str_lit.get().len();
let llbytes = C_uint(bcx.ccx(), bytes);
let llcstr = C_cstr(bcx.ccx(), str_lit, false);
let llcstr = llvm::LLVMConstPointerCast(llcstr, Type::i8p(bcx.ccx()).to_ref());
Store(bcx, llcstr, GEPi(bcx, lldest, &[0u, abi::FAT_PTR_ADDR]));
Store(bcx, llbytes, GEPi(bcx, lldest, &[0u, abi::FAT_PTR_EXTRA]));
bcx
}
let bytes = str_lit.get().len();
let llbytes = C_uint(bcx.ccx(), bytes);
let llcstr = C_cstr(bcx.ccx(), str_lit, false);
let llcstr = consts::ptrcast(llcstr, Type::i8p(bcx.ccx()));
Store(bcx, llcstr, GEPi(bcx, lldest, &[0u, abi::FAT_PTR_ADDR]));
Store(bcx, llbytes, GEPi(bcx, lldest, &[0u, abi::FAT_PTR_EXTRA]));
bcx
}
}
}
......
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(macro_rules)]
use std::collections::Bitv;
use std::default::Default;
use std::iter::FromIterator;
use std::option::IntoIter as OptionIter;
use std::rand::Rand;
use std::rand::XorShiftRng as DummyRng;
// FIXME the glob std::prelude::*; import of Vec is missing non-static inherent methods.
use std::vec::Vec;
#[derive(PartialEq, Eq)]
struct Newt<T>(T);
fn id<T>(x: T) -> T { x }
fn eq<T: Eq>(a: T, b: T) -> bool { a == b }
fn u8_as_i8(x: u8) -> i8 { x as i8 }
fn odd(x: uint) -> bool { x % 2 == 1 }
fn dummy_rng() -> DummyRng { DummyRng::new_unseeded() }
macro_rules! tests {
($($expr:expr: $ty:ty /($($test:expr),*);)+) => (pub fn main() {$({
const C: $ty = $expr;
static S: $ty = $expr;
assert!(eq(C($($test),*), $expr($($test),*)));
assert!(eq(S($($test),*), $expr($($test),*)));
assert!(eq(C($($test),*), S($($test),*)));
})+})
}
tests! {
// Free function.
id: fn(int) -> int /(5);
id::<int>: fn(int) -> int /(5);
// Enum variant constructor.
Some: fn(int) -> Option<int> /(5);
Some::<int>: fn(int) -> Option<int> /(5);
// Tuple struct constructor.
Newt: fn(int) -> Newt<int> /(5);
Newt::<int>: fn(int) -> Newt<int> /(5);
// Inherent static methods.
Vec::new: fn() -> Vec<()> /();
Vec::<()>::new: fn() -> Vec<()> /();
Vec::with_capacity: fn(uint) -> Vec<()> /(5);
Vec::<()>::with_capacity: fn(uint) -> Vec<()> /(5);
Bitv::from_fn: fn(uint, fn(uint) -> bool) -> Bitv /(5, odd);
Bitv::from_fn::<fn(uint) -> bool>: fn(uint, fn(uint) -> bool) -> Bitv /(5, odd);
// Inherent non-static method.
Vec::map_in_place: fn(Vec<u8>, fn(u8) -> i8) -> Vec<i8>
/(vec![b'f', b'o', b'o'], u8_as_i8);
Vec::map_in_place::<i8, fn(u8) -> i8>: fn(Vec<u8>, fn(u8) -> i8) -> Vec<i8>
/(vec![b'f', b'o', b'o'], u8_as_i8);
// FIXME these break with "type parameter might not appear here pointing at `<u8>`.
// Vec::<u8>::map_in_place: fn(Vec<u8>, fn(u8) -> i8) -> Vec<i8>
// /(vec![b'f', b'o', b'o'], u8_as_i8);
// Vec::<u8>::map_in_place::<i8, fn(u8) -> i8>: fn(Vec<u8>, fn(u8) -> i8) -> Vec<i8>
// /(vec![b'f', b'o', b'o'], u8_as_i8);
// Trait static methods.
// FIXME qualified path expressions aka UFCS i.e. <T as Trait>::method.
Default::default: fn() -> int /();
Rand::rand: fn(&mut DummyRng) -> int /(&mut dummy_rng());
Rand::rand::<DummyRng>: fn(&mut DummyRng) -> int /(&mut dummy_rng());
// Trait non-static methods.
Clone::clone: fn(&int) -> int /(&5);
FromIterator::from_iter: fn(OptionIter<int>) -> Vec<int> /(Some(5).into_iter());
FromIterator::from_iter::<OptionIter<int>>: fn(OptionIter<int>) -> Vec<int>
/(Some(5).into_iter());
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册