Auto merge of #26110 - nrc:save-api-3, r=brson

r? @huonw
This commit is contained in:
bors 2015-06-14 22:47:55 +00:00
commit 4f3c19f547
3 changed files with 331 additions and 207 deletions

View file

@ -54,6 +54,15 @@ use super::recorder::{Recorder, FmtStrs};
use util::ppaux;
macro_rules! down_cast_data {
($id:ident, $kind:ident, $this:ident, $sp:expr) => {
let $id = if let super::Data::$kind(data) = $id {
data
} else {
$this.sess.span_bug($sp, &format!("unexpected data kind: {:?}", $id));
};
};
}
pub struct DumpCsvVisitor<'l, 'tcx: 'l> {
save_ctxt: SaveContext<'l, 'tcx>,
@ -249,7 +258,7 @@ impl <'l, 'tcx> DumpCsvVisitor<'l, 'tcx> {
match def {
def::DefMod(_) |
def::DefForeignMod(_) => Some(recorder::ModRef),
def::DefStruct(_) => Some(recorder::StructRef),
def::DefStruct(_) => Some(recorder::TypeRef),
def::DefTy(..) |
def::DefAssociatedTy(..) |
def::DefTrait(_) => Some(recorder::TypeRef),
@ -419,48 +428,31 @@ impl <'l, 'tcx> DumpCsvVisitor<'l, 'tcx> {
id);
}
fn process_trait_ref(&mut self,
trait_ref: &ast::TraitRef) {
match self.lookup_type_ref(trait_ref.ref_id) {
Some(id) => {
let sub_span = self.span.sub_span_for_type_name(trait_ref.path.span);
self.fmt.ref_str(recorder::TypeRef,
trait_ref.path.span,
sub_span,
id,
self.cur_scope);
visit::walk_path(self, &trait_ref.path);
},
None => ()
fn process_trait_ref(&mut self, trait_ref: &ast::TraitRef) {
let trait_ref_data = self.save_ctxt.get_trait_ref_data(trait_ref, self.cur_scope);
if let Some(trait_ref_data) = trait_ref_data {
self.fmt.ref_str(recorder::TypeRef,
trait_ref.path.span,
Some(trait_ref_data.span),
trait_ref_data.ref_id,
trait_ref_data.scope);
visit::walk_path(self, &trait_ref.path);
}
}
fn process_struct_field_def(&mut self,
field: &ast::StructField,
qualname: &str,
scope_id: NodeId) {
match field.node.kind {
ast::NamedField(ident, _) => {
let name = get_ident(ident);
let qualname = format!("{}::{}", qualname, name);
let typ =
ppaux::ty_to_string(
&self.analysis.ty_cx,
*self.analysis.ty_cx.node_types().get(&field.node.id).unwrap());
match self.span.sub_span_before_token(field.span, token::Colon) {
Some(sub_span) => self.fmt.field_str(field.span,
Some(sub_span),
field.node.id,
&name,
&qualname,
&typ,
scope_id),
None => self.sess.span_bug(field.span,
&format!("Could not find sub-span for field {}",
qualname)),
}
},
_ => (),
parent_id: NodeId) {
let field_data = self.save_ctxt.get_field_data(field, parent_id);
if let Some(field_data) = field_data {
down_cast_data!(field_data, VariableData, self, field.span);
self.fmt.field_str(field.span,
Some(field_data.span),
field_data.id,
&field_data.name,
&field_data.qualname,
&field_data.type_value,
field_data.scope);
}
}
@ -497,19 +489,16 @@ impl <'l, 'tcx> DumpCsvVisitor<'l, 'tcx> {
ty_params: &ast::Generics,
body: &ast::Block) {
let fn_data = self.save_ctxt.get_item_data(item);
if let super::Data::FunctionData(fn_data) = fn_data {
self.fmt.fn_str(item.span,
Some(fn_data.span),
fn_data.id,
&fn_data.qualname,
fn_data.scope);
down_cast_data!(fn_data, FunctionData, self, item.span);
self.fmt.fn_str(item.span,
Some(fn_data.span),
fn_data.id,
&fn_data.qualname,
fn_data.scope);
self.process_formals(&decl.inputs, &fn_data.qualname);
self.process_generic_params(ty_params, item.span, &fn_data.qualname, item.id);
} else {
self.sess.span_bug(item.span, "expected FunctionData");
}
self.process_formals(&decl.inputs, &fn_data.qualname);
self.process_generic_params(ty_params, item.span, &fn_data.qualname, item.id);
for arg in &decl.inputs {
self.visit_ty(&arg.ty);
@ -528,18 +517,15 @@ impl <'l, 'tcx> DumpCsvVisitor<'l, 'tcx> {
expr: &ast::Expr)
{
let var_data = self.save_ctxt.get_item_data(item);
if let super::Data::VariableData(var_data) = var_data {
self.fmt.static_str(item.span,
Some(var_data.span),
var_data.id,
&var_data.name,
&var_data.qualname,
&var_data.value,
&var_data.type_value,
var_data.scope);
} else {
self.sess.span_bug(item.span, "expected VariableData");
}
down_cast_data!(var_data, VariableData, self, item.span);
self.fmt.static_str(item.span,
Some(var_data.span),
var_data.id,
&var_data.name,
&var_data.qualname,
&var_data.value,
&var_data.type_value,
var_data.scope);
self.visit_ty(&typ);
self.visit_expr(expr);
@ -593,8 +579,8 @@ impl <'l, 'tcx> DumpCsvVisitor<'l, 'tcx> {
// fields
for field in &def.fields {
self.process_struct_field_def(field, &qualname, item.id);
self.visit_ty(&*field.node.ty);
self.process_struct_field_def(field, item.id);
self.visit_ty(&field.node.ty);
}
self.process_generic_params(ty_params, item.span, &qualname, item.id);
@ -605,59 +591,57 @@ impl <'l, 'tcx> DumpCsvVisitor<'l, 'tcx> {
enum_definition: &ast::EnumDef,
ty_params: &ast::Generics) {
let enum_data = self.save_ctxt.get_item_data(item);
if let super::Data::EnumData(enum_data) = enum_data {
self.fmt.enum_str(item.span,
Some(enum_data.span),
enum_data.id,
&enum_data.qualname,
self.cur_scope,
&enum_data.value);
for variant in &enum_definition.variants {
let name = &get_ident(variant.node.name);
let mut qualname = enum_data.qualname.clone();
qualname.push_str("::");
qualname.push_str(name);
let val = self.span.snippet(variant.span);
match variant.node.kind {
ast::TupleVariantKind(ref args) => {
// first ident in span is the variant's name
self.fmt.tuple_variant_str(variant.span,
self.span.span_for_first_ident(variant.span),
variant.node.id,
name,
&qualname,
&enum_data.qualname,
&val,
item.id);
for arg in args {
self.visit_ty(&*arg.ty);
}
}
ast::StructVariantKind(ref struct_def) => {
let ctor_id = match struct_def.ctor_id {
Some(node_id) => node_id,
None => -1,
};
self.fmt.struct_variant_str(variant.span,
self.span.span_for_first_ident(variant.span),
variant.node.id,
ctor_id,
&qualname,
&enum_data.qualname,
&val,
item.id);
down_cast_data!(enum_data, EnumData, self, item.span);
self.fmt.enum_str(item.span,
Some(enum_data.span),
enum_data.id,
&enum_data.qualname,
enum_data.scope,
&enum_data.value);
for field in &struct_def.fields {
self.process_struct_field_def(field, &qualname, variant.node.id);
self.visit_ty(&*field.node.ty);
}
for variant in &enum_definition.variants {
let name = &get_ident(variant.node.name);
let mut qualname = enum_data.qualname.clone();
qualname.push_str("::");
qualname.push_str(name);
let val = self.span.snippet(variant.span);
match variant.node.kind {
ast::TupleVariantKind(ref args) => {
// first ident in span is the variant's name
self.fmt.tuple_variant_str(variant.span,
self.span.span_for_first_ident(variant.span),
variant.node.id,
name,
&qualname,
&enum_data.qualname,
&val,
enum_data.id);
for arg in args {
self.visit_ty(&*arg.ty);
}
}
ast::StructVariantKind(ref struct_def) => {
let ctor_id = match struct_def.ctor_id {
Some(node_id) => node_id,
None => -1,
};
self.fmt.struct_variant_str(variant.span,
self.span.span_for_first_ident(variant.span),
variant.node.id,
ctor_id,
&qualname,
&enum_data.qualname,
&val,
enum_data.id);
for field in &struct_def.fields {
self.process_struct_field_def(field, variant.node.id);
self.visit_ty(&*field.node.ty);
}
}
}
self.process_generic_params(ty_params, item.span, &enum_data.qualname, item.id);
} else {
self.sess.span_bug(item.span, "expected EnumData");
}
self.process_generic_params(ty_params, item.span, &enum_data.qualname, enum_data.id);
}
fn process_impl(&mut self,
@ -666,45 +650,36 @@ impl <'l, 'tcx> DumpCsvVisitor<'l, 'tcx> {
trait_ref: &Option<ast::TraitRef>,
typ: &ast::Ty,
impl_items: &[P<ast::ImplItem>]) {
let trait_id = trait_ref.as_ref().and_then(|tr| self.lookup_type_ref(tr.ref_id));
match typ.node {
// Common case impl for a struct or something basic.
ast::TyPath(None, ref path) => {
let sub_span = self.span.sub_span_for_type_name(path.span);
let self_id = self.lookup_type_ref(typ.id).map(|id| {
self.fmt.ref_str(recorder::TypeRef,
path.span,
sub_span,
id,
self.cur_scope);
id
});
self.fmt.impl_str(path.span,
sub_span,
item.id,
self_id,
trait_id,
self.cur_scope);
},
_ => {
// Less useful case, impl for a compound type.
self.visit_ty(&*typ);
let sub_span = self.span.sub_span_for_type_name(typ.span);
self.fmt.impl_str(typ.span,
sub_span,
item.id,
None,
trait_id,
self.cur_scope);
let impl_data = self.save_ctxt.get_item_data(item);
down_cast_data!(impl_data, ImplData, self, item.span);
match impl_data.self_ref {
Some(ref self_ref) => {
self.fmt.ref_str(recorder::TypeRef,
item.span,
Some(self_ref.span),
self_ref.ref_id,
self_ref.scope);
}
None => {
self.visit_ty(&typ);
}
}
match *trait_ref {
Some(ref trait_ref) => self.process_trait_ref(trait_ref),
None => (),
if let Some(ref trait_ref_data) = impl_data.trait_ref {
self.fmt.ref_str(recorder::TypeRef,
item.span,
Some(trait_ref_data.span),
trait_ref_data.ref_id,
trait_ref_data.scope);
visit::walk_path(self, &trait_ref.as_ref().unwrap().path);
}
self.fmt.impl_str(item.span,
Some(impl_data.span),
impl_data.id,
impl_data.self_ref.map(|data| data.ref_id),
impl_data.trait_ref.map(|data| data.ref_id),
impl_data.scope);
self.process_generic_params(type_parameters, item.span, "", item.id);
for impl_item in impl_items {
self.visit_impl_item(impl_item);
@ -765,16 +740,13 @@ impl <'l, 'tcx> DumpCsvVisitor<'l, 'tcx> {
fn process_mod(&mut self,
item: &ast::Item) { // The module in question, represented as an item.
let mod_data = self.save_ctxt.get_item_data(item);
if let super::Data::ModData(mod_data) = mod_data {
self.fmt.mod_str(item.span,
Some(mod_data.span),
mod_data.id,
&mod_data.qualname,
mod_data.scope,
&mod_data.filename);
} else {
self.sess.span_bug(item.span, "expected ModData");
}
down_cast_data!(mod_data, ModData, self, item.span);
self.fmt.mod_str(item.span,
Some(mod_data.span),
mod_data.id,
&mod_data.qualname,
mod_data.scope,
&mod_data.filename);
}
fn process_path(&mut self,
@ -804,7 +776,7 @@ impl <'l, 'tcx> DumpCsvVisitor<'l, 'tcx> {
sub_span,
def.def_id(),
self.cur_scope),
def::DefStruct(def_id) => self.fmt.ref_str(recorder::StructRef,
def::DefStruct(def_id) => self.fmt.ref_str(recorder::TypeRef,
span,
sub_span,
def_id,
@ -901,44 +873,33 @@ impl <'l, 'tcx> DumpCsvVisitor<'l, 'tcx> {
self.write_sub_paths_truncated(path, false);
let ty = &ty::expr_ty_adjusted(&self.analysis.ty_cx, ex).sty;
let struct_def = match *ty {
ty::TyStruct(def_id, _) => {
let sub_span = self.span.span_for_last_ident(path.span);
self.fmt.ref_str(recorder::StructRef,
path.span,
sub_span,
def_id,
self.cur_scope);
Some(def_id)
}
_ => None
};
if let Some(struct_lit_data) = self.save_ctxt.get_expr_data(ex) {
down_cast_data!(struct_lit_data, TypeRefData, self, ex.span);
self.fmt.ref_str(recorder::TypeRef,
ex.span,
Some(struct_lit_data.span),
struct_lit_data.ref_id,
struct_lit_data.scope);
let struct_def = struct_lit_data.ref_id;
for field in fields {
match struct_def {
Some(struct_def) => {
let fields = ty::lookup_struct_fields(&self.analysis.ty_cx, struct_def);
for f in &fields {
if generated_code(field.ident.span) {
continue;
}
if f.name == field.ident.node.name {
// We don't really need a sub-span here, but no harm done
let sub_span = self.span.span_for_last_ident(field.ident.span);
self.fmt.ref_str(recorder::VarRef,
field.ident.span,
sub_span,
f.id,
self.cur_scope);
}
}
for field in fields {
if generated_code(field.ident.span) {
continue;
}
None => {}
}
self.visit_expr(&*field.expr)
let field_data = self.save_ctxt.get_field_ref_data(field,
struct_def,
self.cur_scope);
self.fmt.ref_str(recorder::VarRef,
field.ident.span,
Some(field_data.span),
field_data.ref_id,
field_data.scope);
self.visit_expr(&field.expr)
}
}
visit::walk_expr_opt(self, base)
}
@ -1174,7 +1135,7 @@ impl<'l, 'tcx, 'v> Visitor<'v> for DumpCsvVisitor<'l, 'tcx> {
self.process_impl(item,
ty_params,
trait_ref,
&**typ,
&typ,
impl_items)
}
ast::ItemTrait(_, ref generics, ref trait_refs, ref methods) =>
@ -1296,15 +1257,13 @@ impl<'l, 'tcx, 'v> Visitor<'v> for DumpCsvVisitor<'l, 'tcx> {
self.visit_expr(&sub_ex);
let field_data = self.save_ctxt.get_expr_data(ex);
if let super::Data::VariableRefData(field_data) = field_data {
if let Some(field_data) = self.save_ctxt.get_expr_data(ex) {
down_cast_data!(field_data, VariableRefData, self, ex.span);
self.fmt.ref_str(recorder::VarRef,
ex.span,
Some(field_data.span),
field_data.ref_id,
field_data.scope);
} else {
self.sess.span_bug(ex.span, "expected VariableRefData");
}
},
ast::ExprTupField(ref sub_ex, idx) => {

View file

@ -10,6 +10,7 @@
use session::Session;
use middle::ty;
use middle::def;
use std::env;
use std::fs::{self, File};
@ -23,9 +24,11 @@ use syntax::parse::token::{self, get_ident, keywords};
use syntax::visit::{self, Visitor};
use syntax::print::pprust::ty_to_string;
use util::ppaux;
use self::span_utils::SpanUtils;
mod span_utils;
mod recorder;
@ -44,22 +47,28 @@ pub struct CrateData {
/// Data for any entity in the Rust language. The actual data contained varied
/// with the kind of entity being queried. See the nested structs for details.
#[derive(Debug)]
pub enum Data {
/// Data for all kinds of functions and methods.
FunctionData(FunctionData),
/// Data for local and global variables (consts and statics).
/// Data for local and global variables (consts and statics), and fields.
VariableData(VariableData),
/// Data for modules.
ModData(ModData),
/// Data for Enums.
EnumData(EnumData),
/// Data for impls.
ImplData(ImplData),
/// Data for the use of some variable (e.g., the use of a local variable, which
/// will refere to that variables declaration).
VariableRefData(VariableRefData),
/// Data for a reference to a type or trait.
TypeRefData(TypeRefData),
}
/// Data for all kinds of functions and methods.
#[derive(Debug)]
pub struct FunctionData {
pub id: NodeId,
pub name: String,
@ -70,6 +79,7 @@ pub struct FunctionData {
}
/// Data for local and global variables (consts and statics).
#[derive(Debug)]
pub struct VariableData {
pub id: NodeId,
pub name: String,
@ -81,6 +91,7 @@ pub struct VariableData {
}
/// Data for modules.
#[derive(Debug)]
pub struct ModData {
pub id: NodeId,
pub name: String,
@ -91,15 +102,30 @@ pub struct ModData {
}
/// Data for enum declarations.
#[derive(Debug)]
pub struct EnumData {
pub id: NodeId,
pub value: String,
pub qualname: String,
pub span: Span,
pub scope: NodeId,
}
#[derive(Debug)]
pub struct ImplData {
pub id: NodeId,
pub span: Span,
pub scope: NodeId,
// FIXME: I'm not really sure inline data is the best way to do this. Seems
// OK in this case, but generalising leads to returning chunks of AST, which
// feels wrong.
pub trait_ref: Option<TypeRefData>,
pub self_ref: Option<TypeRefData>,
}
/// Data for the use of some item (e.g., the use of a local variable, which
/// will refere to that variables declaration (by ref_id)).
#[derive(Debug)]
pub struct VariableRefData {
pub name: String,
pub span: Span,
@ -107,6 +133,14 @@ pub struct VariableRefData {
pub ref_id: DefId,
}
/// Data for a reference to a type or trait.
#[derive(Debug)]
pub struct TypeRefData {
pub span: Span,
pub scope: NodeId,
pub ref_id: DefId,
}
impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
pub fn new(sess: &'l Session,
@ -209,8 +243,42 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
value: val,
span: sub_span.unwrap(),
qualname: enum_name,
scope: self.analysis.ty_cx.map.get_parent(item.id),
})
},
ast::ItemImpl(_, _, _, ref trait_ref, ref typ, _) => {
let mut type_data = None;
let sub_span;
let parent = self.analysis.ty_cx.map.get_parent(item.id);
match typ.node {
// Common case impl for a struct or something basic.
ast::TyPath(None, ref path) => {
sub_span = self.span_utils.sub_span_for_type_name(path.span);
type_data = self.lookup_ref_id(typ.id).map(|id| TypeRefData {
span: sub_span.unwrap(),
scope: parent,
ref_id: id,
});
},
_ => {
// Less useful case, impl for a compound type.
sub_span = self.span_utils.sub_span_for_type_name(typ.span);
}
}
let trait_data =
trait_ref.as_ref().and_then(|tr| self.get_trait_ref_data(tr, parent));
Data::ImplData(ImplData {
id: item.id,
span: sub_span.unwrap(),
scope: parent,
trait_ref: trait_data,
self_ref: type_data,
})
}
_ => {
// FIXME
unimplemented!();
@ -218,7 +286,50 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
}
}
pub fn get_expr_data(&self, expr: &ast::Expr) -> Data {
// FIXME: we ought to be able to get the parent id ourselves, but we can't
// for now.
pub fn get_field_data(&self, field: &ast::StructField, parent: NodeId) -> Option<Data> {
match field.node.kind {
ast::NamedField(ident, _) => {
let name = get_ident(ident);
let qualname = format!("::{}::{}",
self.analysis.ty_cx.map.path_to_string(parent),
name);
let typ = ppaux::ty_to_string(&self.analysis.ty_cx,
*self.analysis.ty_cx.node_types()
.get(&field.node.id).unwrap());
let sub_span = self.span_utils.sub_span_before_token(field.span, token::Colon);
Some(Data::VariableData(VariableData {
id: field.node.id,
name: get_ident(ident).to_string(),
qualname: qualname,
span: sub_span.unwrap(),
scope: parent,
value: "".to_owned(),
type_value: typ,
}))
},
_ => None,
}
}
// FIXME: we ought to be able to get the parent id ourselves, but we can't
// for now.
pub fn get_trait_ref_data(&self,
trait_ref: &ast::TraitRef,
parent: NodeId)
-> Option<TypeRefData> {
self.lookup_ref_id(trait_ref.ref_id).map(|def_id| {
let sub_span = self.span_utils.sub_span_for_type_name(trait_ref.path.span);
TypeRefData {
span: sub_span.unwrap(),
scope: parent,
ref_id: def_id,
}
})
}
pub fn get_expr_data(&self, expr: &ast::Expr) -> Option<Data> {
match expr.node {
ast::ExprField(ref sub_ex, ident) => {
let ty = &ty::expr_ty_adjusted(&self.analysis.ty_cx, &sub_ex).sty;
@ -228,12 +339,12 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
for f in &fields {
if f.name == ident.node.name {
let sub_span = self.span_utils.span_for_last_ident(expr.span);
return Data::VariableRefData(VariableRefData {
return Some(Data::VariableRefData(VariableRefData {
name: get_ident(ident.node).to_string(),
span: sub_span.unwrap(),
scope: self.analysis.ty_cx.map.get_parent(expr.id),
ref_id: f.id,
});
}));
}
}
@ -242,8 +353,29 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
&get_ident(ident.node),
ty))
}
_ => self.sess.span_bug(expr.span,
&format!("Expected struct type, found {:?}", ty)),
_ => {
debug!("Expected struct type, found {:?}", ty);
None
}
}
}
ast::ExprStruct(ref path, _, _) => {
let ty = &ty::expr_ty_adjusted(&self.analysis.ty_cx, expr).sty;
match *ty {
ty::TyStruct(def_id, _) => {
let sub_span = self.span_utils.span_for_last_ident(path.span);
Some(Data::TypeRefData(TypeRefData {
span: sub_span.unwrap(),
scope: self.analysis.ty_cx.map.get_parent(expr.id),
ref_id: def_id,
}))
}
_ => {
// FIXME ty could legitimately be a TyEnum, but then we will fail
// later if we try to look up the fields.
debug!("expected TyStruct, found {:?}", ty);
None
}
}
}
_ => {
@ -253,10 +385,47 @@ impl<'l, 'tcx: 'l> SaveContext<'l, 'tcx> {
}
}
pub fn get_field_ref_data(&self,
field_ref: &ast::Field,
struct_id: DefId,
parent: NodeId)
-> VariableRefData {
let fields = ty::lookup_struct_fields(&self.analysis.ty_cx, struct_id);
let field_name = get_ident(field_ref.ident.node).to_string();
for f in &fields {
if f.name == field_ref.ident.node.name {
// We don't really need a sub-span here, but no harm done
let sub_span = self.span_utils.span_for_last_ident(field_ref.ident.span);
return VariableRefData {
name: field_name,
span: sub_span.unwrap(),
scope: parent,
ref_id: f.id,
};
}
}
self.sess.span_bug(field_ref.span,
&format!("Couldn't find field {}", field_name));
}
pub fn get_data_for_id(&self, _id: &NodeId) -> Data {
// FIXME
unimplemented!();
}
fn lookup_ref_id(&self, ref_id: NodeId) -> Option<DefId> {
if !self.analysis.ty_cx.def_map.borrow().contains_key(&ref_id) {
self.sess.bug(&format!("def_map has no key for {} in lookup_type_ref",
ref_id));
}
let def = self.analysis.ty_cx.def_map.borrow().get(&ref_id).unwrap().full_def();
match def {
def::DefPrimTy(_) => None,
_ => Some(def.def_id()),
}
}
}
// An AST visitor for collecting paths from patterns.
@ -284,7 +453,7 @@ impl<'v> Visitor<'v> for PathCollector {
self.collected_paths.push((p.id,
path.clone(),
ast::MutMutable,
recorder::StructRef));
recorder::TypeRef));
}
ast::PatEnum(ref path, _) |
ast::PatQPath(_, ref path) => {

View file

@ -89,7 +89,6 @@ pub enum Row {
ModRef,
VarRef,
TypeRef,
StructRef,
FnRef,
}
@ -150,9 +149,6 @@ impl<'a> FmtStrs<'a> {
TypeRef => ("type_ref",
vec!("refid","refidcrate","qualname","scopeid"),
true, true),
StructRef => ("struct_ref",
vec!("refid","refidcrate","qualname","scopeid"),
true, true),
FnRef => ("fn_ref", vec!("refid","refidcrate","qualname","scopeid"), true, true)
}
}