Auto merge of #46664 - mikeyhew:raw_pointer_self, r=arielb1
arbitrary_self_types: add support for raw pointer `self` types This adds support for raw pointer `self` types, under the `arbitrary_self_types` feature flag. Types like `self: *const Self`, `self: *const Rc<Self>`, `self: Rc<*const Self` are all supported. Object safety checks are updated to allow`self: *const Self` and `self: *mut Self`. This PR does not add support for `*const self` and `*mut self` syntax. That can be added in a later PR once this code is reviewed and merged. #44874 r? @arielb1
This commit is contained in:
commit
a9f047c048
@ -59,9 +59,7 @@ impl ObjectSafetyViolation {
|
||||
ObjectSafetyViolation::Method(name, MethodViolationCode::Generic) =>
|
||||
format!("method `{}` has generic type parameters", name).into(),
|
||||
ObjectSafetyViolation::Method(name, MethodViolationCode::NonStandardSelfType) =>
|
||||
format!("method `{}` has a non-standard `self` type. Only `&self`, \
|
||||
`&mut self`, and `Box<Self>` are currently supported \
|
||||
for trait objects", name).into(),
|
||||
format!("method `{}` has a non-standard `self` type", name).into(),
|
||||
ObjectSafetyViolation::AssociatedConst(name) =>
|
||||
format!("the trait cannot contain associated consts like `{}`", name).into(),
|
||||
}
|
||||
|
@ -1191,6 +1191,7 @@ fn needs_drop_raw<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
pub enum ExplicitSelf<'tcx> {
|
||||
ByValue,
|
||||
ByReference(ty::Region<'tcx>, hir::Mutability),
|
||||
ByRawPointer(hir::Mutability),
|
||||
ByBox,
|
||||
Other
|
||||
}
|
||||
@ -1231,10 +1232,15 @@ impl<'tcx> ExplicitSelf<'tcx> {
|
||||
|
||||
match self_arg_ty.sty {
|
||||
_ if is_self_ty(self_arg_ty) => ByValue,
|
||||
ty::TyRef(region, ty::TypeAndMut { ty, mutbl}) if is_self_ty(ty) => {
|
||||
ty::TyRef(region, ty::TypeAndMut { ty, mutbl }) if is_self_ty(ty) => {
|
||||
ByReference(region, mutbl)
|
||||
}
|
||||
ty::TyAdt(def, _) if def.is_box() && is_self_ty(self_arg_ty.boxed_ty()) => ByBox,
|
||||
ty::TyRawPtr(ty::TypeAndMut { ty, mutbl }) if is_self_ty(ty) => {
|
||||
ByRawPointer(mutbl)
|
||||
}
|
||||
ty::TyAdt(def, _) if def.is_box() && is_self_ty(self_arg_ty.boxed_ty()) => {
|
||||
ByBox
|
||||
}
|
||||
_ => Other
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ pub struct Autoderef<'a, 'gcx: 'tcx, 'tcx: 'a> {
|
||||
cur_ty: Ty<'tcx>,
|
||||
obligations: Vec<traits::PredicateObligation<'tcx>>,
|
||||
at_start: bool,
|
||||
include_raw_pointers: bool,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
@ -76,12 +77,13 @@ impl<'a, 'gcx, 'tcx> Iterator for Autoderef<'a, 'gcx, 'tcx> {
|
||||
}
|
||||
|
||||
// Otherwise, deref if type is derefable:
|
||||
let (kind, new_ty) = if let Some(mt) = self.cur_ty.builtin_deref(false, NoPreference) {
|
||||
(AutoderefKind::Builtin, mt.ty)
|
||||
} else {
|
||||
let ty = self.overloaded_deref_ty(self.cur_ty)?;
|
||||
(AutoderefKind::Overloaded, ty)
|
||||
};
|
||||
let (kind, new_ty) =
|
||||
if let Some(mt) = self.cur_ty.builtin_deref(self.include_raw_pointers, NoPreference) {
|
||||
(AutoderefKind::Builtin, mt.ty)
|
||||
} else {
|
||||
let ty = self.overloaded_deref_ty(self.cur_ty)?;
|
||||
(AutoderefKind::Overloaded, ty)
|
||||
};
|
||||
|
||||
if new_ty.references_error() {
|
||||
return None;
|
||||
@ -194,6 +196,15 @@ impl<'a, 'gcx, 'tcx> Autoderef<'a, 'gcx, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
/// also dereference through raw pointer types
|
||||
/// e.g. assuming ptr_to_Foo is the type `*const Foo`
|
||||
/// fcx.autoderef(span, ptr_to_Foo) => [*const Foo]
|
||||
/// fcx.autoderef(span, ptr_to_Foo).include_raw_ptrs() => [*const Foo, Foo]
|
||||
pub fn include_raw_pointers(mut self) -> Self {
|
||||
self.include_raw_pointers = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn finalize(self) {
|
||||
let fcx = self.fcx;
|
||||
fcx.register_predicates(self.into_obligations());
|
||||
@ -212,6 +223,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
||||
cur_ty: self.resolve_type_vars_if_possible(&base_ty),
|
||||
obligations: vec![],
|
||||
at_start: true,
|
||||
include_raw_pointers: false,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
@ -276,6 +276,7 @@ impl<'a, 'gcx, 'tcx> ConfirmContext<'a, 'gcx, 'tcx> {
|
||||
// FIXME: this feels, like, super dubious
|
||||
self.fcx
|
||||
.autoderef(self.span, self_ty)
|
||||
.include_raw_pointers()
|
||||
.filter_map(|(ty, _)| {
|
||||
match ty.sty {
|
||||
ty::TyDynamic(ref data, ..) => data.principal().map(|p| closure(self, ty, p)),
|
||||
|
@ -77,6 +77,12 @@ impl<'a, 'gcx, 'tcx> Deref for ProbeContext<'a, 'gcx, 'tcx> {
|
||||
struct CandidateStep<'tcx> {
|
||||
self_ty: Ty<'tcx>,
|
||||
autoderefs: usize,
|
||||
// true if the type results from a dereference of a raw pointer.
|
||||
// when assembling candidates, we include these steps, but not when
|
||||
// picking methods. This so that if we have `foo: *const Foo` and `Foo` has methods
|
||||
// `fn by_raw_ptr(self: *const Self)` and `fn by_ref(&self)`, then
|
||||
// `foo.by_raw_ptr()` will work and `foo.by_ref()` won't.
|
||||
from_unsafe_deref: bool,
|
||||
unsize: bool,
|
||||
}
|
||||
|
||||
@ -257,6 +263,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
||||
vec![CandidateStep {
|
||||
self_ty,
|
||||
autoderefs: 0,
|
||||
from_unsafe_deref: false,
|
||||
unsize: false,
|
||||
}]
|
||||
};
|
||||
@ -289,14 +296,21 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
||||
-> Option<Vec<CandidateStep<'tcx>>> {
|
||||
// FIXME: we don't need to create the entire steps in one pass
|
||||
|
||||
let mut autoderef = self.autoderef(span, self_ty);
|
||||
let mut autoderef = self.autoderef(span, self_ty).include_raw_pointers();
|
||||
let mut reached_raw_pointer = false;
|
||||
let mut steps: Vec<_> = autoderef.by_ref()
|
||||
.map(|(ty, d)| {
|
||||
CandidateStep {
|
||||
let step = CandidateStep {
|
||||
self_ty: ty,
|
||||
autoderefs: d,
|
||||
from_unsafe_deref: reached_raw_pointer,
|
||||
unsize: false,
|
||||
};
|
||||
if let ty::TyRawPtr(_) = ty.sty {
|
||||
// all the subsequent steps will be from_unsafe_deref
|
||||
reached_raw_pointer = true;
|
||||
}
|
||||
step
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -307,9 +321,20 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
||||
// a real method lookup, this is a hard error (it's an
|
||||
// ambiguity and we can't make progress).
|
||||
if !is_suggestion.0 {
|
||||
let t = self.structurally_resolved_type(span, final_ty);
|
||||
assert_eq!(t, self.tcx.types.err);
|
||||
return None
|
||||
if reached_raw_pointer
|
||||
&& !self.tcx.sess.features.borrow().arbitrary_self_types {
|
||||
// only produce a warning in this case, because inference variables used to
|
||||
// be allowed here in some cases for raw pointers
|
||||
struct_span_warn!(self.tcx.sess, span, E0619,
|
||||
"the type of this value must be known in this context")
|
||||
.note("this will be made into a hard error in a future version of \
|
||||
the compiler")
|
||||
.emit();
|
||||
} else {
|
||||
let t = self.structurally_resolved_type(span, final_ty);
|
||||
assert_eq!(t, self.tcx.types.err);
|
||||
return None
|
||||
}
|
||||
} else {
|
||||
// If we're just looking for suggestions,
|
||||
// though, ambiguity is no big thing, we can
|
||||
@ -322,6 +347,9 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
||||
steps.push(CandidateStep {
|
||||
self_ty: self.tcx.mk_slice(elem_ty),
|
||||
autoderefs: dereferences,
|
||||
// this could be from an unsafe deref if we had
|
||||
// a *mut/const [T; N]
|
||||
from_unsafe_deref: reached_raw_pointer,
|
||||
unsize: true,
|
||||
});
|
||||
}
|
||||
@ -830,7 +858,9 @@ impl<'a, 'gcx, 'tcx> ProbeContext<'a, 'gcx, 'tcx> {
|
||||
.iter()
|
||||
.filter(|step| {
|
||||
debug!("pick_core: step={:?}", step);
|
||||
!step.self_ty.references_error()
|
||||
// skip types that are from a type error or that would require dereferencing
|
||||
// a raw pointer
|
||||
!step.self_ty.references_error() && !step.from_unsafe_deref
|
||||
}).flat_map(|step| {
|
||||
self.pick_by_value_method(step).or_else(|| {
|
||||
self.pick_autorefd_method(step, hir::MutImmutable).or_else(|| {
|
||||
|
@ -503,7 +503,7 @@ impl<'a, 'gcx> CheckTypeWellFormedVisitor<'a, 'gcx> {
|
||||
&ty::Binder(self_arg_ty)
|
||||
);
|
||||
|
||||
let mut autoderef = fcx.autoderef(span, self_arg_ty);
|
||||
let mut autoderef = fcx.autoderef(span, self_arg_ty).include_raw_pointers();
|
||||
|
||||
loop {
|
||||
if let Some((potential_self_ty, _)) = autoderef.next() {
|
||||
@ -532,12 +532,32 @@ impl<'a, 'gcx> CheckTypeWellFormedVisitor<'a, 'gcx> {
|
||||
let is_self_ty = |ty| fcx.infcx.can_eq(fcx.param_env, self_ty, ty).is_ok();
|
||||
let self_kind = ExplicitSelf::determine(self_arg_ty, is_self_ty);
|
||||
|
||||
if let ExplicitSelf::Other = self_kind {
|
||||
if !fcx.tcx.sess.features.borrow().arbitrary_self_types {
|
||||
feature_gate::feature_err(&fcx.tcx.sess.parse_sess, "arbitrary_self_types", span,
|
||||
GateIssue::Language, "arbitrary `self` types are unstable")
|
||||
.help("consider changing to `self`, `&self`, `&mut self`, or `self: Box<Self>`")
|
||||
.emit();
|
||||
if !fcx.tcx.sess.features.borrow().arbitrary_self_types {
|
||||
match self_kind {
|
||||
ExplicitSelf::ByValue |
|
||||
ExplicitSelf::ByReference(_, _) |
|
||||
ExplicitSelf::ByBox => (),
|
||||
|
||||
ExplicitSelf::ByRawPointer(_) => {
|
||||
feature_gate::feature_err(
|
||||
&fcx.tcx.sess.parse_sess,
|
||||
"arbitrary_self_types",
|
||||
span,
|
||||
GateIssue::Language,
|
||||
"raw pointer `self` is unstable")
|
||||
.help("consider changing to `self`, `&self`, `&mut self`, or `self: Box<Self>`")
|
||||
.emit();
|
||||
}
|
||||
|
||||
ExplicitSelf::Other => {
|
||||
feature_gate::feature_err(
|
||||
&fcx.tcx.sess.parse_sess,
|
||||
"arbitrary_self_types",
|
||||
span,
|
||||
GateIssue::Language,"arbitrary `self` types are unstable")
|
||||
.help("consider changing to `self`, `&self`, `&mut self`, or `self: Box<Self>`")
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
37
src/test/run-pass/arbitrary_self_types_raw_pointer_struct.rs
Normal file
37
src/test/run-pass/arbitrary_self_types_raw_pointer_struct.rs
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2015 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(arbitrary_self_types)]
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
struct Foo(String);
|
||||
|
||||
impl Foo {
|
||||
unsafe fn foo(self: *const Self) -> *const str {
|
||||
(*self).0.as_ref()
|
||||
}
|
||||
|
||||
fn complicated_1(self: *const Rc<Self>) -> &'static str {
|
||||
"Foo::complicated_1"
|
||||
}
|
||||
|
||||
unsafe fn complicated_2(self: Rc<*const Self>) -> *const str {
|
||||
(**self).0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let foo = Foo("abc123".into());
|
||||
assert_eq!("abc123", unsafe { &*(&foo as *const Foo).foo() });
|
||||
assert_eq!("Foo::complicated_1", std::ptr::null::<Rc<Foo>>().complicated_1());
|
||||
let rc = Rc::new(&foo as *const Foo);
|
||||
assert_eq!("abc123", unsafe { &*rc.complicated_2()});
|
||||
}
|
70
src/test/run-pass/arbitrary_self_types_raw_pointer_trait.rs
Normal file
70
src/test/run-pass/arbitrary_self_types_raw_pointer_trait.rs
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright 2015 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(arbitrary_self_types)]
|
||||
|
||||
use std::ptr;
|
||||
|
||||
trait Foo {
|
||||
fn foo(self: *const Self) -> &'static str;
|
||||
|
||||
unsafe fn bar(self: *const Self) -> i64;
|
||||
|
||||
unsafe fn complicated(self: *const *const Self) -> i64 where Self: Sized {
|
||||
(*self).bar()
|
||||
}
|
||||
}
|
||||
|
||||
impl Foo for i32 {
|
||||
fn foo(self: *const Self) -> &'static str {
|
||||
"I'm an i32!"
|
||||
}
|
||||
|
||||
unsafe fn bar(self: *const Self) -> i64 {
|
||||
*self as i64
|
||||
}
|
||||
}
|
||||
|
||||
impl Foo for u32 {
|
||||
fn foo(self: *const Self) -> &'static str {
|
||||
"I'm a u32!"
|
||||
}
|
||||
|
||||
unsafe fn bar(self: *const Self) -> i64 {
|
||||
*self as i64
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let null_i32 = ptr::null::<i32>() as *const Foo;
|
||||
let null_u32 = ptr::null::<u32>() as *const Foo;
|
||||
|
||||
assert_eq!("I'm an i32!", null_i32.foo());
|
||||
assert_eq!("I'm a u32!", null_u32.foo());
|
||||
|
||||
let valid_i32 = 5i32;
|
||||
let valid_i32_thin = &valid_i32 as *const i32;
|
||||
assert_eq!("I'm an i32!", valid_i32_thin.foo());
|
||||
assert_eq!(5, unsafe { valid_i32_thin.bar() });
|
||||
assert_eq!(5, unsafe { (&valid_i32_thin as *const *const i32).complicated() });
|
||||
let valid_i32_fat = valid_i32_thin as *const Foo;
|
||||
assert_eq!("I'm an i32!", valid_i32_fat.foo());
|
||||
assert_eq!(5, unsafe { valid_i32_fat.bar() });
|
||||
|
||||
let valid_u32 = 18u32;
|
||||
let valid_u32_thin = &valid_u32 as *const u32;
|
||||
assert_eq!("I'm a u32!", valid_u32_thin.foo());
|
||||
assert_eq!(18, unsafe { valid_u32_thin.bar() });
|
||||
assert_eq!(18, unsafe { (&valid_u32_thin as *const *const u32).complicated() });
|
||||
let valid_u32_fat = valid_u32_thin as *const Foo;
|
||||
assert_eq!("I'm a u32!", valid_u32_fat.foo());
|
||||
assert_eq!(18, unsafe { valid_u32_fat.bar() });
|
||||
|
||||
}
|
@ -4,7 +4,7 @@ error[E0038]: the trait `Foo` cannot be made into an object
|
||||
40 | let x = Box::new(5usize) as Box<Foo>;
|
||||
| ^^^^^^^^ the trait `Foo` cannot be made into an object
|
||||
|
|
||||
= note: method `foo` has a non-standard `self` type. Only `&self`, `&mut self`, and `Box<Self>` are currently supported for trait objects
|
||||
= note: method `foo` has a non-standard `self` type
|
||||
|
||||
error[E0038]: the trait `Foo` cannot be made into an object
|
||||
--> $DIR/arbitrary-self-types-not-object-safe.rs:40:13
|
||||
@ -12,7 +12,7 @@ error[E0038]: the trait `Foo` cannot be made into an object
|
||||
40 | let x = Box::new(5usize) as Box<Foo>;
|
||||
| ^^^^^^^^^^^^^^^^ the trait `Foo` cannot be made into an object
|
||||
|
|
||||
= note: method `foo` has a non-standard `self` type. Only `&self`, `&mut self`, and `Box<Self>` are currently supported for trait objects
|
||||
= note: method `foo` has a non-standard `self` type
|
||||
= note: required because of the requirements on the impl of `std::ops::CoerceUnsized<std::boxed::Box<Foo>>` for `std::boxed::Box<usize>`
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
28
src/test/ui/feature-gate-arbitrary_self_types-raw-pointer.rs
Normal file
28
src/test/ui/feature-gate-arbitrary_self_types-raw-pointer.rs
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
struct Foo;
|
||||
|
||||
impl Foo {
|
||||
fn foo(self: *const Self) {}
|
||||
//~^ ERROR raw pointer `self` is unstable
|
||||
}
|
||||
|
||||
trait Bar {
|
||||
fn bar(self: *const Self);
|
||||
//~^ ERROR raw pointer `self` is unstable
|
||||
}
|
||||
|
||||
impl Bar for () {
|
||||
fn bar(self: *const Self) {}
|
||||
//~^ ERROR raw pointer `self` is unstable
|
||||
}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,29 @@
|
||||
error: raw pointer `self` is unstable (see issue #44874)
|
||||
--> $DIR/feature-gate-arbitrary_self_types-raw-pointer.rs:19:18
|
||||
|
|
||||
19 | fn bar(self: *const Self);
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= help: add #![feature(arbitrary_self_types)] to the crate attributes to enable
|
||||
= help: consider changing to `self`, `&self`, `&mut self`, or `self: Box<Self>`
|
||||
|
||||
error: raw pointer `self` is unstable (see issue #44874)
|
||||
--> $DIR/feature-gate-arbitrary_self_types-raw-pointer.rs:14:18
|
||||
|
|
||||
14 | fn foo(self: *const Self) {}
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= help: add #![feature(arbitrary_self_types)] to the crate attributes to enable
|
||||
= help: consider changing to `self`, `&self`, `&mut self`, or `self: Box<Self>`
|
||||
|
||||
error: raw pointer `self` is unstable (see issue #44874)
|
||||
--> $DIR/feature-gate-arbitrary_self_types-raw-pointer.rs:24:18
|
||||
|
|
||||
24 | fn bar(self: *const Self) {}
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= help: add #![feature(arbitrary_self_types)] to the crate attributes to enable
|
||||
= help: consider changing to `self`, `&self`, `&mut self`, or `self: Box<Self>`
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
||||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
@ -8,11 +8,12 @@
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
struct S(String);
|
||||
// must-compile-successfully
|
||||
|
||||
impl S {
|
||||
fn f(self: *mut S) -> String { self.0 }
|
||||
//~^ ERROR invalid `self` type
|
||||
// tests that the following code compiles, but produces a future-compatibility warning
|
||||
|
||||
fn main() {
|
||||
let data = std::ptr::null();
|
||||
let _ = &data as *const *const ();
|
||||
if data.is_null() {}
|
||||
}
|
||||
|
||||
fn main() { S("".to_owned()).f(); }
|
8
src/test/ui/inference-variable-behind-raw-pointer.stderr
Normal file
8
src/test/ui/inference-variable-behind-raw-pointer.stderr
Normal file
@ -0,0 +1,8 @@
|
||||
warning[E0619]: the type of this value must be known in this context
|
||||
--> $DIR/inference-variable-behind-raw-pointer.rs:18:13
|
||||
|
|
||||
18 | if data.is_null() {}
|
||||
| ^^^^^^^
|
||||
|
|
||||
= note: this will be made into a hard error in a future version of the compiler
|
||||
|
Loading…
Reference in New Issue
Block a user