mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
Implement offset_of in hir-def and hir-ty
This commit is contained in:
parent
9b8eb807a3
commit
15048304e3
@ -31,7 +31,7 @@ use crate::{
|
||||
hir::{
|
||||
dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy,
|
||||
ClosureKind, Expr, ExprId, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability,
|
||||
Pat, PatId, RecordFieldPat, RecordLitField, Statement,
|
||||
OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
|
||||
},
|
||||
item_scope::BuiltinShadowMode,
|
||||
lang_item::LangItem,
|
||||
@ -649,7 +649,11 @@ impl ExprCollector<'_> {
|
||||
}
|
||||
ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr),
|
||||
ast::Expr::AsmExpr(_) => self.missing_expr(),
|
||||
ast::Expr::OffsetOfExpr(_) => self.missing_expr(),
|
||||
ast::Expr::OffsetOfExpr(e) => {
|
||||
let container = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty()));
|
||||
let fields = e.fields().map(|it| it.as_name()).collect();
|
||||
self.alloc_expr(Expr::OffsetOf(OffsetOf { container, fields }), syntax_ptr)
|
||||
}
|
||||
ast::Expr::FormatArgsExpr(_) => self.missing_expr(),
|
||||
})
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
use std::fmt::{self, Write};
|
||||
|
||||
use hir_expand::db::ExpandDatabase;
|
||||
use itertools::Itertools;
|
||||
use syntax::ast::HasName;
|
||||
|
||||
use crate::{
|
||||
@ -154,6 +155,15 @@ impl Printer<'_> {
|
||||
match expr {
|
||||
Expr::Missing => w!(self, "<EFBFBD>"),
|
||||
Expr::Underscore => w!(self, "_"),
|
||||
Expr::OffsetOf(offset_of) => {
|
||||
w!(self, "builtin#offset_of!(");
|
||||
self.print_type_ref(&offset_of.container);
|
||||
w!(
|
||||
self,
|
||||
", {})",
|
||||
offset_of.fields.iter().format_with(".", |field, f| f(&field.display(self.db)))
|
||||
);
|
||||
}
|
||||
Expr::Path(path) => self.print_path(path),
|
||||
Expr::If { condition, then_branch, else_branch } => {
|
||||
w!(self, "if ");
|
||||
|
@ -281,6 +281,13 @@ pub enum Expr {
|
||||
Array(Array),
|
||||
Literal(Literal),
|
||||
Underscore,
|
||||
OffsetOf(OffsetOf),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OffsetOf {
|
||||
pub container: Interned<TypeRef>,
|
||||
pub fields: Box<[Name]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -341,7 +348,7 @@ impl Expr {
|
||||
pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
|
||||
match self {
|
||||
Expr::Missing => {}
|
||||
Expr::Path(_) => {}
|
||||
Expr::Path(_) | Expr::OffsetOf(_) => {}
|
||||
Expr::If { condition, then_branch, else_branch } => {
|
||||
f(*condition);
|
||||
f(*then_branch);
|
||||
|
@ -42,12 +42,22 @@ fn main() {
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
expect![[r##"
|
||||
#[rustc_builtin_macro]
|
||||
macro_rules! column {() => {}}
|
||||
macro_rules! asm {() => {}}
|
||||
|
||||
fn main() { 0 as u32; }
|
||||
"#]],
|
||||
fn main() {
|
||||
let i: u64 = 3;
|
||||
let o: u64;
|
||||
unsafe {
|
||||
builtin #asm ( {
|
||||
$crate::format_args!("mov {0}, {1}");
|
||||
$crate::format_args!("add {0}, 5");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
"##]],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -419,9 +419,9 @@ fn asm_expand(
|
||||
|
||||
let pound = quote! {@PUNCT '#'};
|
||||
let expanded = quote! {
|
||||
builtin #pound asm {
|
||||
##literals
|
||||
}
|
||||
builtin #pound asm (
|
||||
{##literals}
|
||||
)
|
||||
};
|
||||
ExpandResult::ok(expanded)
|
||||
}
|
||||
|
@ -452,6 +452,7 @@ impl InferenceContext<'_> {
|
||||
|
||||
fn walk_expr_without_adjust(&mut self, tgt_expr: ExprId) {
|
||||
match &self.body[tgt_expr] {
|
||||
Expr::OffsetOf(_) => (),
|
||||
Expr::If { condition, then_branch, else_branch } => {
|
||||
self.consume_expr(*condition);
|
||||
self.consume_expr(*then_branch);
|
||||
|
@ -843,6 +843,7 @@ impl InferenceContext<'_> {
|
||||
});
|
||||
expected
|
||||
}
|
||||
Expr::OffsetOf(_) => TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner),
|
||||
};
|
||||
// use a new type variable if we got unknown here
|
||||
let ty = self.insert_type_vars_shallow(ty);
|
||||
|
@ -35,6 +35,7 @@ impl InferenceContext<'_> {
|
||||
fn infer_mut_expr_without_adjust(&mut self, tgt_expr: ExprId, mutability: Mutability) {
|
||||
match &self.body[tgt_expr] {
|
||||
Expr::Missing => (),
|
||||
Expr::OffsetOf(_) => (),
|
||||
&Expr::If { condition, then_branch, else_branch } => {
|
||||
self.infer_mut_expr(condition, Mutability::Not);
|
||||
self.infer_mut_expr(then_branch, Mutability::Not);
|
||||
|
@ -370,6 +370,9 @@ impl<'ctx> MirLowerCtx<'ctx> {
|
||||
mut current: BasicBlockId,
|
||||
) -> Result<Option<BasicBlockId>> {
|
||||
match &self.body.exprs[expr_id] {
|
||||
Expr::OffsetOf(_) => {
|
||||
not_supported!("builtin#offset_of")
|
||||
}
|
||||
Expr::Missing => {
|
||||
if let DefWithBodyId::FunctionId(f) = self.owner {
|
||||
let assoc = f.lookup(self.db.upcast());
|
||||
|
@ -2,55 +2,6 @@ use expect_test::expect;
|
||||
|
||||
use super::{check, check_infer, check_no_mismatches, check_types};
|
||||
|
||||
#[test]
|
||||
fn infer_box() {
|
||||
check_types(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
fn test() {
|
||||
let x = box 1;
|
||||
let t = (x, box x, box &1, box [1]);
|
||||
t;
|
||||
} //^ (Box<i32>, Box<Box<i32>>, Box<&i32>, Box<[i32; 1]>)
|
||||
|
||||
//- /std.rs crate:std
|
||||
#[prelude_import] use prelude::*;
|
||||
mod prelude {}
|
||||
|
||||
mod boxed {
|
||||
#[lang = "owned_box"]
|
||||
pub struct Box<T: ?Sized> {
|
||||
inner: *mut T,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_box_with_allocator() {
|
||||
check_types(
|
||||
r#"
|
||||
//- /main.rs crate:main deps:std
|
||||
fn test() {
|
||||
let x = box 1;
|
||||
let t = (x, box x, box &1, box [1]);
|
||||
t;
|
||||
} //^ (Box<i32, {unknown}>, Box<Box<i32, {unknown}>, {unknown}>, Box<&i32, {unknown}>, Box<[i32; 1], {unknown}>)
|
||||
|
||||
//- /std.rs crate:std
|
||||
#[prelude_import] use prelude::*;
|
||||
mod boxed {
|
||||
#[lang = "owned_box"]
|
||||
pub struct Box<T: ?Sized, A: Allocator> {
|
||||
inner: *mut T,
|
||||
allocator: A,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_adt_self() {
|
||||
check_types(
|
||||
@ -2763,8 +2714,8 @@ impl<T> [T] {
|
||||
}
|
||||
|
||||
fn test() {
|
||||
let vec = <[_]>::into_vec(box [1i32]);
|
||||
let v: Vec<Box<dyn B>> = <[_]> :: into_vec(box [box Astruct]);
|
||||
let vec = <[_]>::into_vec(#[rustc_box] Box::new([1i32]));
|
||||
let v: Vec<Box<dyn B>> = <[_]> :: into_vec(#[rustc_box] Box::new([#[rustc_box] Box::new(Astruct)]));
|
||||
}
|
||||
|
||||
trait B{}
|
||||
@ -2774,20 +2725,20 @@ impl B for Astruct {}
|
||||
expect![[r#"
|
||||
604..608 'self': Box<[T], A>
|
||||
637..669 '{ ... }': Vec<T, A>
|
||||
683..796 '{ ...t]); }': ()
|
||||
683..853 '{ ...])); }': ()
|
||||
693..696 'vec': Vec<i32, Global>
|
||||
699..714 '<[_]>::into_vec': fn into_vec<i32, Global>(Box<[i32], Global>) -> Vec<i32, Global>
|
||||
699..726 '<[_]>:...1i32])': Vec<i32, Global>
|
||||
715..725 'box [1i32]': Box<[i32; 1], Global>
|
||||
719..725 '[1i32]': [i32; 1]
|
||||
720..724 '1i32': i32
|
||||
736..737 'v': Vec<Box<dyn B, Global>, Global>
|
||||
757..774 '<[_]> ...to_vec': fn into_vec<Box<dyn B, Global>, Global>(Box<[Box<dyn B, Global>], Global>) -> Vec<Box<dyn B, Global>, Global>
|
||||
757..793 '<[_]> ...ruct])': Vec<Box<dyn B, Global>, Global>
|
||||
775..792 'box [b...truct]': Box<[Box<dyn B, Global>; 1], Global>
|
||||
779..792 '[box Astruct]': [Box<dyn B, Global>; 1]
|
||||
780..791 'box Astruct': Box<Astruct, Global>
|
||||
784..791 'Astruct': Astruct
|
||||
699..745 '<[_]>:...i32]))': Vec<i32, Global>
|
||||
715..744 '#[rust...1i32])': Box<[i32; 1], Global>
|
||||
737..743 '[1i32]': [i32; 1]
|
||||
738..742 '1i32': i32
|
||||
755..756 'v': Vec<Box<dyn B, Global>, Global>
|
||||
776..793 '<[_]> ...to_vec': fn into_vec<Box<dyn B, Global>, Global>(Box<[Box<dyn B, Global>], Global>) -> Vec<Box<dyn B, Global>, Global>
|
||||
776..850 '<[_]> ...ct)]))': Vec<Box<dyn B, Global>, Global>
|
||||
794..849 '#[rust...uct)])': Box<[Box<dyn B, Global>; 1], Global>
|
||||
816..848 '[#[rus...ruct)]': [Box<dyn B, Global>; 1]
|
||||
817..847 '#[rust...truct)': Box<Astruct, Global>
|
||||
839..846 'Astruct': Astruct
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
@ -3649,3 +3600,15 @@ fn main() {
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offset_of() {
|
||||
check_types(
|
||||
r#"
|
||||
fn main() {
|
||||
builtin#offset_of((,), 0);
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^ usize
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
@ -162,16 +162,16 @@ unsafe impl Allocator for Global {}
|
||||
|
||||
#[lang = "owned_box"]
|
||||
#[fundamental]
|
||||
pub struct Box<T: ?Sized, A: Allocator = Global>;
|
||||
pub struct Box<T: ?Sized, A: Allocator = Global>(T);
|
||||
|
||||
impl<T: ?Sized + Unsize<U>, U: ?Sized, A: Allocator> CoerceUnsized<Box<U, A>> for Box<T, A> {}
|
||||
|
||||
fn send() -> Box<dyn Future<Output = ()> + Send + 'static>{
|
||||
box async move {}
|
||||
Box(async move {})
|
||||
}
|
||||
|
||||
fn not_send() -> Box<dyn Future<Output = ()> + 'static> {
|
||||
box async move {}
|
||||
Box(async move {})
|
||||
}
|
||||
"#,
|
||||
);
|
||||
@ -3057,7 +3057,7 @@ impl<T: ?Sized> core::ops::Deref for Box<T> {
|
||||
|
||||
fn foo() {
|
||||
let s = None;
|
||||
let f: Box<dyn FnOnce(&Option<i32>)> = box (|ps| {});
|
||||
let f: Box<dyn FnOnce(&Option<i32>)> = Box { inner: &mut (|ps| {}) };
|
||||
f(&s);
|
||||
}"#,
|
||||
expect![[r#"
|
||||
@ -3068,19 +3068,19 @@ fn foo() {
|
||||
186..197 '*self.inner': T
|
||||
187..191 'self': &Box<T>
|
||||
187..197 'self.inner': *mut T
|
||||
218..308 '{ ...&s); }': ()
|
||||
218..324 '{ ...&s); }': ()
|
||||
228..229 's': Option<i32>
|
||||
232..236 'None': Option<i32>
|
||||
246..247 'f': Box<dyn FnOnce(&Option<i32>)>
|
||||
281..294 'box (|ps| {})': Box<impl Fn(&Option<i32>)>
|
||||
286..293 '|ps| {}': impl Fn(&Option<i32>)
|
||||
287..289 'ps': &Option<i32>
|
||||
291..293 '{}': ()
|
||||
300..301 'f': Box<dyn FnOnce(&Option<i32>)>
|
||||
300..305 'f(&s)': ()
|
||||
302..304 '&s': &Option<i32>
|
||||
303..304 's': Option<i32>
|
||||
281..294: expected Box<dyn FnOnce(&Option<i32>)>, got Box<impl Fn(&Option<i32>)>
|
||||
281..310 'Box { ... {}) }': Box<dyn FnOnce(&Option<i32>)>
|
||||
294..308 '&mut (|ps| {})': &mut impl Fn(&Option<i32>)
|
||||
300..307 '|ps| {}': impl Fn(&Option<i32>)
|
||||
301..303 'ps': &Option<i32>
|
||||
305..307 '{}': ()
|
||||
316..317 'f': Box<dyn FnOnce(&Option<i32>)>
|
||||
316..321 'f(&s)': ()
|
||||
318..320 '&s': &Option<i32>
|
||||
319..320 's': Option<i32>
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
@ -220,24 +220,39 @@ fn tuple_expr(p: &mut Parser<'_>) -> CompletedMarker {
|
||||
// fn foo() {
|
||||
// builtin#asm(0);
|
||||
// builtin#format_args(0);
|
||||
// builtin#builtin(0);
|
||||
// builtin#offset_of(Foo, bar.baz.0);
|
||||
// }
|
||||
fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> {
|
||||
let m = p.start();
|
||||
p.bump_remap(T![builtin]);
|
||||
p.bump(T![#]);
|
||||
if p.at_contextual_kw(T![offset_of]) {
|
||||
p.bump_remap(T![offset_of]);
|
||||
p.expect(T!['(']);
|
||||
type_(p);
|
||||
p.bump(T![,]);
|
||||
p.expect(T![,]);
|
||||
while !p.at(EOF) && !p.at(T![')']) {
|
||||
if p.at(IDENT) || p.at(INT_NUMBER) {
|
||||
name_ref_or_index(p);
|
||||
// } else if p.at(FLOAT_NUMBER) {
|
||||
// FIXME: needs float hack
|
||||
} else {
|
||||
p.err_and_bump("expected field name or number");
|
||||
}
|
||||
if !p.at(T![')']) {
|
||||
p.expect(T![.]);
|
||||
}
|
||||
}
|
||||
p.expect(T![')']);
|
||||
Some(m.complete(p, OFFSET_OF_EXPR))
|
||||
} else if p.at_contextual_kw(T![format_args]) {
|
||||
p.bump_remap(T![format_args]);
|
||||
p.expect(T!['(']);
|
||||
expr(p);
|
||||
p.expect(T![')']);
|
||||
Some(m.complete(p, FORMAT_ARGS_EXPR))
|
||||
} else if p.at_contextual_kw(T![asm]) {
|
||||
p.bump_remap(T![asm]);
|
||||
p.expect(T!['(']);
|
||||
expr(p);
|
||||
p.expect(T![')']);
|
||||
|
@ -1,90 +0,0 @@
|
||||
SOURCE_FILE
|
||||
FN
|
||||
FN_KW "fn"
|
||||
WHITESPACE " "
|
||||
NAME
|
||||
IDENT "foo"
|
||||
PARAM_LIST
|
||||
L_PAREN "("
|
||||
R_PAREN ")"
|
||||
WHITESPACE " "
|
||||
BLOCK_EXPR
|
||||
STMT_LIST
|
||||
L_CURLY "{"
|
||||
WHITESPACE "\n "
|
||||
LET_STMT
|
||||
LET_KW "let"
|
||||
WHITESPACE " "
|
||||
IDENT_PAT
|
||||
NAME
|
||||
IDENT "x"
|
||||
WHITESPACE " "
|
||||
EQ "="
|
||||
WHITESPACE " "
|
||||
BOX_EXPR
|
||||
BOX_KW "box"
|
||||
WHITESPACE " "
|
||||
LITERAL
|
||||
INT_NUMBER "1i32"
|
||||
SEMICOLON ";"
|
||||
WHITESPACE "\n "
|
||||
LET_STMT
|
||||
LET_KW "let"
|
||||
WHITESPACE " "
|
||||
IDENT_PAT
|
||||
NAME
|
||||
IDENT "y"
|
||||
WHITESPACE " "
|
||||
EQ "="
|
||||
WHITESPACE " "
|
||||
TUPLE_EXPR
|
||||
L_PAREN "("
|
||||
BOX_EXPR
|
||||
BOX_KW "box"
|
||||
WHITESPACE " "
|
||||
LITERAL
|
||||
INT_NUMBER "1i32"
|
||||
COMMA ","
|
||||
WHITESPACE " "
|
||||
BOX_EXPR
|
||||
BOX_KW "box"
|
||||
WHITESPACE " "
|
||||
LITERAL
|
||||
INT_NUMBER "2i32"
|
||||
R_PAREN ")"
|
||||
SEMICOLON ";"
|
||||
WHITESPACE "\n "
|
||||
LET_STMT
|
||||
LET_KW "let"
|
||||
WHITESPACE " "
|
||||
IDENT_PAT
|
||||
NAME
|
||||
IDENT "z"
|
||||
WHITESPACE " "
|
||||
EQ "="
|
||||
WHITESPACE " "
|
||||
CALL_EXPR
|
||||
PATH_EXPR
|
||||
PATH
|
||||
PATH_SEGMENT
|
||||
NAME_REF
|
||||
IDENT "Foo"
|
||||
ARG_LIST
|
||||
L_PAREN "("
|
||||
BOX_EXPR
|
||||
BOX_KW "box"
|
||||
WHITESPACE " "
|
||||
LITERAL
|
||||
INT_NUMBER "1i32"
|
||||
COMMA ","
|
||||
WHITESPACE " "
|
||||
BOX_EXPR
|
||||
BOX_KW "box"
|
||||
WHITESPACE " "
|
||||
LITERAL
|
||||
INT_NUMBER "2i32"
|
||||
R_PAREN ")"
|
||||
SEMICOLON ";"
|
||||
WHITESPACE "\n"
|
||||
R_CURLY "}"
|
||||
WHITESPACE "\n"
|
@ -1,5 +0,0 @@
|
||||
fn foo() {
|
||||
let x = box 1i32;
|
||||
let y = (box 1i32, box 2i32);
|
||||
let z = Foo(box 1i32, box 2i32);
|
||||
}
|
@ -376,7 +376,7 @@ Expr =
|
||||
| UnderscoreExpr
|
||||
|
||||
OffsetOfExpr =
|
||||
Attr* 'builtin' '#' 'offset_of' '(' ')'
|
||||
Attr* 'builtin' '#' 'offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')'
|
||||
|
||||
AsmExpr =
|
||||
Attr* 'builtin' '#' 'asm' '(' ')'
|
||||
|
@ -1014,6 +1014,9 @@ impl OffsetOfExpr {
|
||||
support::token(&self.syntax, T![offset_of])
|
||||
}
|
||||
pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
|
||||
pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
|
||||
pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
|
||||
pub fn fields(&self) -> AstChildren<NameRef> { support::children(&self.syntax) }
|
||||
pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
|
||||
}
|
||||
|
||||
|
@ -623,7 +623,7 @@ fn lower_enum(grammar: &Grammar, rule: &Rule) -> Option<Vec<String>> {
|
||||
}
|
||||
|
||||
fn lower_rule(acc: &mut Vec<Field>, grammar: &Grammar, label: Option<&String>, rule: &Rule) {
|
||||
if lower_comma_list(acc, grammar, label, rule) {
|
||||
if lower_seperated_list(acc, grammar, label, rule) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -689,7 +689,7 @@ fn lower_rule(acc: &mut Vec<Field>, grammar: &Grammar, label: Option<&String>, r
|
||||
}
|
||||
|
||||
// (T (',' T)* ','?)
|
||||
fn lower_comma_list(
|
||||
fn lower_seperated_list(
|
||||
acc: &mut Vec<Field>,
|
||||
grammar: &Grammar,
|
||||
label: Option<&String>,
|
||||
@ -699,19 +699,23 @@ fn lower_comma_list(
|
||||
Rule::Seq(it) => it,
|
||||
_ => return false,
|
||||
};
|
||||
let (node, repeat, trailing_comma) = match rule.as_slice() {
|
||||
[Rule::Node(node), Rule::Rep(repeat), Rule::Opt(trailing_comma)] => {
|
||||
(node, repeat, trailing_comma)
|
||||
let (node, repeat, trailing_sep) = match rule.as_slice() {
|
||||
[Rule::Node(node), Rule::Rep(repeat), Rule::Opt(trailing_sep)] => {
|
||||
(node, repeat, Some(trailing_sep))
|
||||
}
|
||||
[Rule::Node(node), Rule::Rep(repeat)] => (node, repeat, None),
|
||||
_ => return false,
|
||||
};
|
||||
let repeat = match &**repeat {
|
||||
Rule::Seq(it) => it,
|
||||
_ => return false,
|
||||
};
|
||||
match repeat.as_slice() {
|
||||
[comma, Rule::Node(n)] if comma == &**trailing_comma && n == node => (),
|
||||
_ => return false,
|
||||
if !matches!(
|
||||
repeat.as_slice(),
|
||||
[comma, Rule::Node(n)]
|
||||
if trailing_sep.map_or(true, |it| comma == &**it) && n == node
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
let ty = grammar[*node].name.clone();
|
||||
let name = label.cloned().unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty)));
|
||||
|
Loading…
x
Reference in New Issue
Block a user