Restructure AST so that the associated type definition carries
bounds like any other "type parameter".
This commit is contained in:
parent
01b81c0ebb
commit
319d778ed3
@ -81,7 +81,7 @@ pub fn encode_inlined_item(ecx: &e::EncodeContext,
|
||||
e::IIForeignRef(i) => i.id,
|
||||
e::IITraitItemRef(_, &ast::ProvidedMethod(ref m)) => m.id,
|
||||
e::IITraitItemRef(_, &ast::RequiredMethod(ref m)) => m.id,
|
||||
e::IITraitItemRef(_, &ast::TypeTraitItem(ref ti)) => ti.id,
|
||||
e::IITraitItemRef(_, &ast::TypeTraitItem(ref ti)) => ti.ty_param.id,
|
||||
e::IIImplItemRef(_, &ast::MethodImplItem(ref m)) => m.id,
|
||||
e::IIImplItemRef(_, &ast::TypeImplItem(ref ti)) => ti.id,
|
||||
};
|
||||
@ -156,7 +156,7 @@ pub fn decode_inlined_item<'tcx>(cdata: &cstore::crate_metadata,
|
||||
match *ti {
|
||||
ast::ProvidedMethod(ref m) => m.pe_ident(),
|
||||
ast::RequiredMethod(ref ty_m) => ty_m.ident,
|
||||
ast::TypeTraitItem(ref ti) => ti.ident,
|
||||
ast::TypeTraitItem(ref ti) => ti.ty_param.ident,
|
||||
}
|
||||
},
|
||||
ast::IIImplItem(_, ref m) => {
|
||||
|
@ -296,8 +296,8 @@ impl<'a, 'tcx, 'v> Visitor<'v> for EmbargoVisitor<'a, 'tcx> {
|
||||
self.exported_items.insert(m.id);
|
||||
}
|
||||
ast::TypeTraitItem(ref t) => {
|
||||
debug!("typedef {}", t.id);
|
||||
self.exported_items.insert(t.id);
|
||||
debug!("typedef {}", t.ty_param.id);
|
||||
self.exported_items.insert(t.ty_param.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1559,19 +1559,19 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
ast::TypeTraitItem(ref associated_type) => {
|
||||
let def = DefAssociatedTy(local_def(
|
||||
associated_type.id));
|
||||
associated_type.ty_param.id));
|
||||
|
||||
let name_bindings =
|
||||
self.add_child(associated_type.ident.name,
|
||||
self.add_child(associated_type.ty_param.ident.name,
|
||||
module_parent.clone(),
|
||||
ForbidDuplicateTypesAndValues,
|
||||
associated_type.span);
|
||||
associated_type.ty_param.span);
|
||||
// NB: not IMPORTABLE
|
||||
name_bindings.define_type(def,
|
||||
associated_type.span,
|
||||
associated_type.ty_param.span,
|
||||
PUBLIC);
|
||||
|
||||
(associated_type.ident.name, TypeTraitItemKind)
|
||||
(associated_type.ty_param.ident.name, TypeTraitItemKind)
|
||||
}
|
||||
};
|
||||
|
||||
@ -4218,7 +4218,7 @@ impl<'a> Resolver<'a> {
|
||||
impl_items.as_slice());
|
||||
}
|
||||
|
||||
ItemTrait(ref generics, ref unbound, ref bounds, ref methods) => {
|
||||
ItemTrait(ref generics, ref unbound, ref bounds, ref trait_items) => {
|
||||
// Create a new rib for the self type.
|
||||
let mut self_type_rib = Rib::new(ItemRibKind);
|
||||
|
||||
@ -4246,13 +4246,13 @@ impl<'a> Resolver<'a> {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
for method in (*methods).iter() {
|
||||
// Create a new rib for the method-specific type
|
||||
for trait_item in (*trait_items).iter() {
|
||||
// Create a new rib for the trait_item-specific type
|
||||
// parameters.
|
||||
//
|
||||
// FIXME #4951: Do we need a node ID here?
|
||||
|
||||
match *method {
|
||||
match *trait_item {
|
||||
ast::RequiredMethod(ref ty_m) => {
|
||||
this.with_type_parameter_rib
|
||||
(HasTypeParameters(&ty_m.generics,
|
||||
@ -4287,8 +4287,9 @@ impl<'a> Resolver<'a> {
|
||||
ProvidedMethod(m.id)),
|
||||
&**m)
|
||||
}
|
||||
ast::TypeTraitItem(_) => {
|
||||
visit::walk_trait_item(this, method);
|
||||
ast::TypeTraitItem(ref data) => {
|
||||
this.resolve_type_parameter(&data.ty_param);
|
||||
visit::walk_trait_item(this, trait_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4477,20 +4478,25 @@ impl<'a> Resolver<'a> {
|
||||
fn resolve_type_parameters(&mut self,
|
||||
type_parameters: &OwnedSlice<TyParam>) {
|
||||
for type_parameter in type_parameters.iter() {
|
||||
for bound in type_parameter.bounds.iter() {
|
||||
self.resolve_type_parameter_bound(type_parameter.id, bound,
|
||||
TraitBoundingTypeParameter);
|
||||
}
|
||||
match &type_parameter.unbound {
|
||||
&Some(ref unbound) =>
|
||||
self.resolve_type_parameter_bound(
|
||||
type_parameter.id, unbound, TraitBoundingTypeParameter),
|
||||
&None => {}
|
||||
}
|
||||
match type_parameter.default {
|
||||
Some(ref ty) => self.resolve_type(&**ty),
|
||||
None => {}
|
||||
}
|
||||
self.resolve_type_parameter(type_parameter);
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_type_parameter(&mut self,
|
||||
type_parameter: &TyParam) {
|
||||
for bound in type_parameter.bounds.iter() {
|
||||
self.resolve_type_parameter_bound(type_parameter.id, bound,
|
||||
TraitBoundingTypeParameter);
|
||||
}
|
||||
match &type_parameter.unbound {
|
||||
&Some(ref unbound) =>
|
||||
self.resolve_type_parameter_bound(
|
||||
type_parameter.id, unbound, TraitBoundingTypeParameter),
|
||||
&None => {}
|
||||
}
|
||||
match type_parameter.default {
|
||||
Some(ref ty) => self.resolve_type(&**ty),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4577,14 +4583,14 @@ impl<'a> Resolver<'a> {
|
||||
self.resolve_error(trait_reference.path.span,
|
||||
format!("`{}` is not a trait",
|
||||
self.path_names_to_string(
|
||||
&trait_reference.path)));
|
||||
&trait_reference.path)));
|
||||
|
||||
// If it's a typedef, give a note
|
||||
match def {
|
||||
DefTy(..) => {
|
||||
self.session.span_note(
|
||||
trait_reference.path.span,
|
||||
format!("`type` aliases cannot \
|
||||
trait_reference.path.span,
|
||||
format!("`type` aliases cannot \
|
||||
be used for traits")
|
||||
.as_slice());
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ impl<'v> Visitor<'v> for Annotator {
|
||||
}
|
||||
}
|
||||
|
||||
TypeTraitItem(ref typedef) => (typedef.id, &typedef.attrs),
|
||||
TypeTraitItem(ref typedef) => (typedef.ty_param.id, &typedef.attrs),
|
||||
};
|
||||
self.annotate(id, attrs, |v| visit::walk_trait_item(v, t));
|
||||
}
|
||||
|
@ -298,7 +298,7 @@ fn collect_trait_methods(ccx: &CrateCtxt,
|
||||
&*m.pe_fn_decl())
|
||||
}
|
||||
ast::TypeTraitItem(ref at) => {
|
||||
tcx.sess.span_bug(at.span,
|
||||
tcx.sess.span_bug(at.ty_param.span,
|
||||
"there shouldn't \
|
||||
be a type trait \
|
||||
item here")
|
||||
@ -315,9 +315,9 @@ fn collect_trait_methods(ccx: &CrateCtxt,
|
||||
ast::TypeTraitItem(ref ast_associated_type) => {
|
||||
let trait_did = local_def(trait_id);
|
||||
let associated_type = ty::AssociatedType {
|
||||
name: ast_associated_type.ident.name,
|
||||
name: ast_associated_type.ty_param.ident.name,
|
||||
vis: ast::Public,
|
||||
def_id: local_def(ast_associated_type.id),
|
||||
def_id: local_def(ast_associated_type.ty_param.id),
|
||||
container: TraitContainer(trait_did),
|
||||
};
|
||||
|
||||
@ -345,7 +345,7 @@ fn collect_trait_methods(ccx: &CrateCtxt,
|
||||
method.id))
|
||||
}
|
||||
ast::TypeTraitItem(ref typedef) => {
|
||||
ty::TypeTraitItemId(local_def(typedef.id))
|
||||
ty::TypeTraitItemId(local_def(typedef.ty_param.id))
|
||||
}
|
||||
}
|
||||
}).collect());
|
||||
@ -463,12 +463,12 @@ fn convert_associated_type(ccx: &CrateCtxt,
|
||||
.get_slice(subst::TypeSpace)
|
||||
.iter()
|
||||
.find(|def| {
|
||||
def.def_id == local_def(associated_type.id)
|
||||
def.def_id == local_def(associated_type.ty_param.id)
|
||||
});
|
||||
let type_parameter_def = match type_parameter_def {
|
||||
Some(type_parameter_def) => type_parameter_def,
|
||||
None => {
|
||||
ccx.tcx().sess.span_bug(associated_type.span,
|
||||
ccx.tcx().sess.span_bug(associated_type.ty_param.span,
|
||||
"`convert_associated_type()` didn't find \
|
||||
a type parameter ID corresponding to \
|
||||
this type")
|
||||
@ -477,18 +477,18 @@ fn convert_associated_type(ccx: &CrateCtxt,
|
||||
let param_type = ty::mk_param(ccx.tcx,
|
||||
subst::TypeSpace,
|
||||
type_parameter_def.index,
|
||||
local_def(associated_type.id));
|
||||
ccx.tcx.tcache.borrow_mut().insert(local_def(associated_type.id),
|
||||
local_def(associated_type.ty_param.id));
|
||||
ccx.tcx.tcache.borrow_mut().insert(local_def(associated_type.ty_param.id),
|
||||
Polytype {
|
||||
generics: ty::Generics::empty(),
|
||||
ty: param_type,
|
||||
});
|
||||
write_ty_to_tcx(ccx.tcx, associated_type.id, param_type);
|
||||
write_ty_to_tcx(ccx.tcx, associated_type.ty_param.id, param_type);
|
||||
|
||||
let associated_type = Rc::new(ty::AssociatedType {
|
||||
name: associated_type.ident.name,
|
||||
name: associated_type.ty_param.ident.name,
|
||||
vis: ast::Public,
|
||||
def_id: local_def(associated_type.id),
|
||||
def_id: local_def(associated_type.ty_param.id),
|
||||
container: TraitContainer(trait_def.trait_ref.def_id),
|
||||
});
|
||||
ccx.tcx
|
||||
@ -978,7 +978,7 @@ impl<'a,'tcx> AstConv<'tcx> for TraitMethodCtxt<'a,'tcx> {
|
||||
match *item {
|
||||
ast::RequiredMethod(_) | ast::ProvidedMethod(_) => {}
|
||||
ast::TypeTraitItem(ref item) => {
|
||||
if local_def(item.id) == associated_type_id {
|
||||
if local_def(item.ty_param.id) == associated_type_id {
|
||||
return ty::mk_param(self.tcx(),
|
||||
subst::TypeSpace,
|
||||
index,
|
||||
@ -1480,7 +1480,7 @@ pub fn trait_def_of_item(ccx: &CrateCtxt, it: &ast::Item) -> Rc<ty::TraitDef> {
|
||||
types.push(ty::mk_param(ccx.tcx,
|
||||
subst::TypeSpace,
|
||||
index,
|
||||
local_def(trait_item.id)))
|
||||
local_def(trait_item.ty_param.id)))
|
||||
}
|
||||
ast::RequiredMethod(_) | ast::ProvidedMethod(_) => {}
|
||||
}
|
||||
@ -1630,11 +1630,11 @@ fn ty_of_trait_item(ccx: &CrateCtxt, trait_item: &ast::TraitItem)
|
||||
"ty_of_trait_item() on provided method")
|
||||
}
|
||||
ast::TypeTraitItem(ref associated_type) => {
|
||||
let parent = ccx.tcx.map.get_parent(associated_type.id);
|
||||
let parent = ccx.tcx.map.get_parent(associated_type.ty_param.id);
|
||||
let trait_def = match ccx.tcx.map.get(parent) {
|
||||
ast_map::NodeItem(item) => trait_def_of_item(ccx, &*item),
|
||||
_ => {
|
||||
ccx.tcx.sess.span_bug(associated_type.span,
|
||||
ccx.tcx.sess.span_bug(associated_type.ty_param.span,
|
||||
"associated type's parent wasn't \
|
||||
an item?!")
|
||||
}
|
||||
@ -1680,8 +1680,8 @@ fn ty_generics_for_trait(ccx: &CrateCtxt,
|
||||
let def = ty::TypeParameterDef {
|
||||
space: subst::TypeSpace,
|
||||
index: generics.types.len(subst::TypeSpace),
|
||||
name: associated_type.ident.name,
|
||||
def_id: local_def(associated_type.id),
|
||||
name: associated_type.ty_param.ident.name,
|
||||
def_id: local_def(associated_type.ty_param.id),
|
||||
bounds: ty::ParamBounds {
|
||||
builtin_bounds: ty::empty_builtin_bounds(),
|
||||
trait_bounds: Vec::new(),
|
||||
@ -1690,7 +1690,7 @@ fn ty_generics_for_trait(ccx: &CrateCtxt,
|
||||
associated_with: Some(local_def(trait_id)),
|
||||
default: None,
|
||||
};
|
||||
ccx.tcx.ty_param_defs.borrow_mut().insert(associated_type.id,
|
||||
ccx.tcx.ty_param_defs.borrow_mut().insert(associated_type.ty_param.id,
|
||||
def.clone());
|
||||
generics.types.push(subst::TypeSpace, def);
|
||||
}
|
||||
@ -1810,9 +1810,9 @@ fn ensure_associated_types<'tcx,AC>(this: &AC, trait_id: ast::DefId)
|
||||
ast::ProvidedMethod(_) => {}
|
||||
ast::TypeTraitItem(ref associated_type) => {
|
||||
let info = ty::AssociatedTypeInfo {
|
||||
def_id: local_def(associated_type.id),
|
||||
def_id: local_def(associated_type.ty_param.id),
|
||||
index: index,
|
||||
name: associated_type.ident.name,
|
||||
name: associated_type.ty_param.ident.name,
|
||||
};
|
||||
result.push(info);
|
||||
index += 1;
|
||||
|
@ -861,10 +861,8 @@ pub enum ImplItem {
|
||||
|
||||
#[deriving(Clone, PartialEq, Eq, Encodable, Decodable, Hash, Show)]
|
||||
pub struct AssociatedType {
|
||||
pub id: NodeId,
|
||||
pub span: Span,
|
||||
pub ident: Ident,
|
||||
pub attrs: Vec<Attribute>,
|
||||
pub ty_param: TyParam,
|
||||
}
|
||||
|
||||
#[deriving(Clone, PartialEq, Eq, Encodable, Decodable, Hash, Show)]
|
||||
|
@ -405,7 +405,9 @@ impl<'ast> Map<'ast> {
|
||||
MethMac(_) => panic!("no path elem for {}", node),
|
||||
}
|
||||
}
|
||||
TypeTraitItem(ref m) => PathName(m.ident.name),
|
||||
TypeTraitItem(ref m) => {
|
||||
PathName(m.ty_param.ident.name)
|
||||
}
|
||||
},
|
||||
NodeVariant(v) => PathName(v.node.name.name),
|
||||
_ => panic!("no path elem for {}", node)
|
||||
@ -510,7 +512,7 @@ impl<'ast> Map<'ast> {
|
||||
match *trait_method {
|
||||
RequiredMethod(ref type_method) => type_method.span,
|
||||
ProvidedMethod(ref method) => method.span,
|
||||
TypeTraitItem(ref typedef) => typedef.span,
|
||||
TypeTraitItem(ref typedef) => typedef.ty_param.span,
|
||||
}
|
||||
}
|
||||
Some(NodeImplItem(ref impl_item)) => {
|
||||
@ -650,7 +652,7 @@ impl Named for TraitItem {
|
||||
match *self {
|
||||
RequiredMethod(ref tm) => tm.ident.name,
|
||||
ProvidedMethod(ref m) => m.name(),
|
||||
TypeTraitItem(ref at) => at.ident.name,
|
||||
TypeTraitItem(ref at) => at.ty_param.ident.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -783,7 +785,7 @@ impl<'ast> Visitor<'ast> for NodeCollector<'ast> {
|
||||
self.insert(m.id, NodeTraitItem(tm));
|
||||
}
|
||||
TypeTraitItem(ref typ) => {
|
||||
self.insert(typ.id, NodeTraitItem(tm));
|
||||
self.insert(typ.ty_param.id, NodeTraitItem(tm));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -976,7 +978,7 @@ pub fn map_decoded_item<'ast, F: FoldOps>(map: &Map<'ast>,
|
||||
let trait_item_id = match *trait_item {
|
||||
ProvidedMethod(ref m) => m.id,
|
||||
RequiredMethod(ref m) => m.id,
|
||||
TypeTraitItem(ref ty) => ty.id,
|
||||
TypeTraitItem(ref ty) => ty.ty_param.id,
|
||||
};
|
||||
|
||||
collector.insert(trait_item_id, NodeTraitItem(trait_item));
|
||||
@ -1080,7 +1082,7 @@ fn node_id_to_string(map: &Map, id: NodeId) -> String {
|
||||
}
|
||||
TypeTraitItem(ref t) => {
|
||||
format!("type item {} in {} (id={})",
|
||||
token::get_ident(t.ident),
|
||||
token::get_ident(t.ty_param.ident),
|
||||
map.path_to_string(id),
|
||||
id)
|
||||
}
|
||||
|
@ -525,7 +525,7 @@ impl<'a, 'v, O: IdVisitingOperation> Visitor<'v> for IdVisitor<'a, O> {
|
||||
match *tm {
|
||||
ast::RequiredMethod(ref m) => self.operation.visit_id(m.id),
|
||||
ast::ProvidedMethod(ref m) => self.operation.visit_id(m.id),
|
||||
ast::TypeTraitItem(ref typ) => self.operation.visit_id(typ.id),
|
||||
ast::TypeTraitItem(ref typ) => self.operation.visit_id(typ.ty_param.id),
|
||||
}
|
||||
visit::walk_trait_item(self, tm);
|
||||
}
|
||||
|
@ -260,7 +260,7 @@ impl<'a, 'v> Visitor<'v> for Context<'a> {
|
||||
ast::RequiredMethod(_) | ast::ProvidedMethod(_) => {}
|
||||
ast::TypeTraitItem(ref ti) => {
|
||||
self.gate_feature("associated_types",
|
||||
ti.span,
|
||||
ti.ty_param.span,
|
||||
"associated types are experimental")
|
||||
}
|
||||
}
|
||||
|
@ -793,19 +793,16 @@ pub fn noop_fold_typedef<T>(t: Typedef, folder: &mut T)
|
||||
|
||||
pub fn noop_fold_associated_type<T>(at: AssociatedType, folder: &mut T)
|
||||
-> AssociatedType
|
||||
where T: Folder {
|
||||
let new_id = folder.new_id(at.id);
|
||||
let new_span = folder.new_span(at.span);
|
||||
let new_ident = folder.fold_ident(at.ident);
|
||||
where T: Folder
|
||||
{
|
||||
let new_attrs = at.attrs
|
||||
.iter()
|
||||
.map(|attr| folder.fold_attribute((*attr).clone()))
|
||||
.collect();
|
||||
let new_param = folder.fold_ty_param(at.ty_param);
|
||||
ast::AssociatedType {
|
||||
ident: new_ident,
|
||||
attrs: new_attrs,
|
||||
id: new_id,
|
||||
span: new_span,
|
||||
ty_param: new_param,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1229,16 +1229,13 @@ impl<'a> Parser<'a> {
|
||||
/// Parses `type Foo;` in a trait declaration only. The `type` keyword has
|
||||
/// already been parsed.
|
||||
fn parse_associated_type(&mut self, attrs: Vec<Attribute>)
|
||||
-> AssociatedType {
|
||||
let lo = self.span.lo;
|
||||
let ident = self.parse_ident();
|
||||
let hi = self.span.hi;
|
||||
-> AssociatedType
|
||||
{
|
||||
let ty_param = self.parse_ty_param();
|
||||
self.expect(&token::Semi);
|
||||
AssociatedType {
|
||||
id: ast::DUMMY_NODE_ID,
|
||||
span: mk_sp(lo, hi),
|
||||
ident: ident,
|
||||
attrs: attrs,
|
||||
ty_param: ty_param,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -818,9 +818,11 @@ impl<'a> State<'a> {
|
||||
}
|
||||
|
||||
fn print_associated_type(&mut self, typedef: &ast::AssociatedType)
|
||||
-> IoResult<()> {
|
||||
-> IoResult<()>
|
||||
{
|
||||
try!(self.print_outer_attributes(typedef.attrs[]));
|
||||
try!(self.word_space("type"));
|
||||
try!(self.print_ident(typedef.ident));
|
||||
try!(self.print_ty_param(&typedef.ty_param));
|
||||
word(&mut self.s, ";")
|
||||
}
|
||||
|
||||
@ -2434,23 +2436,7 @@ impl<'a> State<'a> {
|
||||
} else {
|
||||
let idx = idx - generics.lifetimes.len();
|
||||
let param = generics.ty_params.get(idx);
|
||||
match param.unbound {
|
||||
Some(TraitTyParamBound(ref tref)) => {
|
||||
try!(s.print_trait_ref(tref));
|
||||
try!(s.word_space("?"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
try!(s.print_ident(param.ident));
|
||||
try!(s.print_bounds(":", ¶m.bounds));
|
||||
match param.default {
|
||||
Some(ref default) => {
|
||||
try!(space(&mut s.s));
|
||||
try!(s.word_space("="));
|
||||
s.print_type(&**default)
|
||||
}
|
||||
_ => Ok(())
|
||||
}
|
||||
s.print_ty_param(param)
|
||||
}
|
||||
}));
|
||||
|
||||
@ -2458,6 +2444,26 @@ impl<'a> State<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_ty_param(&mut self, param: &ast::TyParam) -> IoResult<()> {
|
||||
match param.unbound {
|
||||
Some(TraitTyParamBound(ref tref)) => {
|
||||
try!(self.print_trait_ref(tref));
|
||||
try!(self.word_space("?"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
try!(self.print_ident(param.ident));
|
||||
try!(self.print_bounds(":", ¶m.bounds));
|
||||
match param.default {
|
||||
Some(ref default) => {
|
||||
try!(space(&mut self.s));
|
||||
try!(self.word_space("="));
|
||||
self.print_type(&**default)
|
||||
}
|
||||
_ => Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_where_clause(&mut self, generics: &ast::Generics)
|
||||
-> IoResult<()> {
|
||||
if generics.where_clause.predicates.len() == 0 {
|
||||
|
@ -596,7 +596,8 @@ pub fn walk_trait_item<'v, V: Visitor<'v>>(visitor: &mut V, trait_method: &'v Tr
|
||||
RequiredMethod(ref method_type) => visitor.visit_ty_method(method_type),
|
||||
ProvidedMethod(ref method) => walk_method_helper(visitor, &**method),
|
||||
TypeTraitItem(ref associated_type) => {
|
||||
visitor.visit_ident(associated_type.span, associated_type.ident)
|
||||
visitor.visit_ident(associated_type.ty_param.span,
|
||||
associated_type.ty_param.ident)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user