Rework enums and type constructors
This commit is contained in:
parent
1593080b8d
commit
a94251e707
@ -8,8 +8,8 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
type_constructor::TypeInvokationConstructor, Evaluate, Evaluation, ExpectedType, Expression,
|
type_constructor::{RawTypeConstructor, TypeInvokationConstructor},
|
||||||
Statement, Type, TypeConstructor, WithPosition,
|
Evaluate, Evaluation, ExpectedType, Expression, Statement, Type, TypeConstructor, WithPosition,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
@ -46,7 +46,7 @@ impl Assignment {
|
|||||||
impl Evaluate for Assignment {
|
impl Evaluate for Assignment {
|
||||||
fn validate(&self, context: &mut Context, manage_memory: bool) -> Result<(), ValidationError> {
|
fn validate(&self, context: &mut Context, manage_memory: bool) -> Result<(), ValidationError> {
|
||||||
if let Some(TypeConstructor::Raw(WithPosition {
|
if let Some(TypeConstructor::Raw(WithPosition {
|
||||||
node: Type::None,
|
node: RawTypeConstructor::None,
|
||||||
position,
|
position,
|
||||||
})) = &self.constructor
|
})) = &self.constructor
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,19 @@ pub enum TypeConstructor {
|
|||||||
Invokation(TypeInvokationConstructor),
|
Invokation(TypeInvokationConstructor),
|
||||||
List(WithPosition<ListTypeConstructor>),
|
List(WithPosition<ListTypeConstructor>),
|
||||||
ListOf(WithPosition<Box<TypeConstructor>>),
|
ListOf(WithPosition<Box<TypeConstructor>>),
|
||||||
Raw(WithPosition<Type>),
|
Raw(WithPosition<RawTypeConstructor>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
pub enum RawTypeConstructor {
|
||||||
|
Any,
|
||||||
|
Boolean,
|
||||||
|
Float,
|
||||||
|
Integer,
|
||||||
|
Map,
|
||||||
|
None,
|
||||||
|
Range,
|
||||||
|
String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeConstructor {
|
impl TypeConstructor {
|
||||||
@ -140,8 +152,8 @@ impl TypeConstructor {
|
|||||||
return_type,
|
return_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TypeConstructor::List(positioned_constructor) => {
|
TypeConstructor::List(constructor) => {
|
||||||
let ListTypeConstructor { length, item_type } = positioned_constructor.node;
|
let ListTypeConstructor { length, item_type } = constructor.node;
|
||||||
let constructed_type = item_type.construct(context)?;
|
let constructed_type = item_type.construct(context)?;
|
||||||
|
|
||||||
Type::List {
|
Type::List {
|
||||||
@ -154,7 +166,16 @@ impl TypeConstructor {
|
|||||||
|
|
||||||
Type::ListOf(Box::new(item_type))
|
Type::ListOf(Box::new(item_type))
|
||||||
}
|
}
|
||||||
TypeConstructor::Raw(r#type) => r#type.node,
|
TypeConstructor::Raw(raw_type) => match raw_type.node {
|
||||||
|
RawTypeConstructor::Any => Type::Any,
|
||||||
|
RawTypeConstructor::Boolean => Type::Boolean,
|
||||||
|
RawTypeConstructor::Float => Type::Float,
|
||||||
|
RawTypeConstructor::Integer => Type::Integer,
|
||||||
|
RawTypeConstructor::Map => Type::Map,
|
||||||
|
RawTypeConstructor::None => Type::None,
|
||||||
|
RawTypeConstructor::Range => Type::Range,
|
||||||
|
RawTypeConstructor::String => Type::String,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(r#type)
|
Ok(r#type)
|
||||||
|
@ -12,7 +12,10 @@ use crate::{
|
|||||||
lexer::{Control, Keyword, Operator, Token},
|
lexer::{Control, Keyword, Operator, Token},
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::{enum_declaration::EnumVariant, type_constructor::TypeInvokationConstructor};
|
use self::{
|
||||||
|
enum_declaration::EnumVariant,
|
||||||
|
type_constructor::{RawTypeConstructor, TypeInvokationConstructor},
|
||||||
|
};
|
||||||
|
|
||||||
pub type ParserInput<'src> =
|
pub type ParserInput<'src> =
|
||||||
SpannedInput<Token<'src>, SimpleSpan, &'src [(Token<'src>, SimpleSpan)]>;
|
SpannedInput<Token<'src>, SimpleSpan, &'src [(Token<'src>, SimpleSpan)]>;
|
||||||
@ -76,15 +79,18 @@ pub fn parser<'src>(
|
|||||||
|
|
||||||
let type_constructor = recursive(|type_constructor| {
|
let type_constructor = recursive(|type_constructor| {
|
||||||
let primitive_type = choice((
|
let primitive_type = choice((
|
||||||
just(Token::Keyword(Keyword::Any)).to(Type::Any),
|
just(Token::Keyword(Keyword::Any)).to(RawTypeConstructor::Any),
|
||||||
just(Token::Keyword(Keyword::Bool)).to(Type::Boolean),
|
just(Token::Keyword(Keyword::Bool)).to(RawTypeConstructor::Boolean),
|
||||||
just(Token::Keyword(Keyword::Float)).to(Type::Float),
|
just(Token::Keyword(Keyword::Float)).to(RawTypeConstructor::Float),
|
||||||
just(Token::Keyword(Keyword::Int)).to(Type::Integer),
|
just(Token::Keyword(Keyword::Int)).to(RawTypeConstructor::Integer),
|
||||||
just(Token::Keyword(Keyword::None)).to(Type::None),
|
just(Token::Keyword(Keyword::Map)).to(RawTypeConstructor::Map),
|
||||||
just(Token::Keyword(Keyword::Range)).to(Type::Range),
|
just(Token::Keyword(Keyword::None)).to(RawTypeConstructor::None),
|
||||||
just(Token::Keyword(Keyword::Str)).to(Type::String),
|
just(Token::Keyword(Keyword::Range)).to(RawTypeConstructor::Range),
|
||||||
|
just(Token::Keyword(Keyword::Str)).to(RawTypeConstructor::String),
|
||||||
))
|
))
|
||||||
.map_with(|r#type, state| TypeConstructor::Raw(r#type.with_position(state.span())));
|
.map_with(|raw_constructor, state| {
|
||||||
|
TypeConstructor::Raw(raw_constructor.with_position(state.span()))
|
||||||
|
});
|
||||||
|
|
||||||
let function_type = just(Token::Keyword(Keyword::Fn))
|
let function_type = just(Token::Keyword(Keyword::Fn))
|
||||||
.ignore_then(
|
.ignore_then(
|
||||||
@ -700,6 +706,7 @@ pub fn parser<'src>(
|
|||||||
positioned_identifier
|
positioned_identifier
|
||||||
.clone()
|
.clone()
|
||||||
.separated_by(just(Token::Control(Control::Comma)))
|
.separated_by(just(Token::Control(Control::Comma)))
|
||||||
|
.allow_trailing()
|
||||||
.collect()
|
.collect()
|
||||||
.delimited_by(
|
.delimited_by(
|
||||||
just(Token::Operator(Operator::Less)),
|
just(Token::Operator(Operator::Less)),
|
||||||
@ -710,6 +717,8 @@ pub fn parser<'src>(
|
|||||||
.then(
|
.then(
|
||||||
enum_variant
|
enum_variant
|
||||||
.separated_by(just(Token::Control(Control::Comma)))
|
.separated_by(just(Token::Control(Control::Comma)))
|
||||||
|
.allow_trailing()
|
||||||
|
.at_least(1)
|
||||||
.collect()
|
.collect()
|
||||||
.delimited_by(
|
.delimited_by(
|
||||||
just(Token::Control(Control::CurlyOpen)),
|
just(Token::Control(Control::CurlyOpen)),
|
||||||
|
@ -12,7 +12,7 @@ fn type_invokation() {
|
|||||||
Some(TypeConstructor::Invokation(TypeInvokationConstructor {
|
Some(TypeConstructor::Invokation(TypeInvokationConstructor {
|
||||||
identifier: Identifier::new("Foo").with_position((3, 6)),
|
identifier: Identifier::new("Foo").with_position((3, 6)),
|
||||||
type_arguments: Some(vec![TypeConstructor::Raw(
|
type_arguments: Some(vec![TypeConstructor::Raw(
|
||||||
Type::Integer.with_position((7, 10))
|
RawTypeConstructor::Integer.with_position((7, 10))
|
||||||
)]),
|
)]),
|
||||||
})),
|
})),
|
||||||
AssignmentOperator::Assign,
|
AssignmentOperator::Assign,
|
||||||
@ -55,20 +55,20 @@ fn enum_declaration() {
|
|||||||
parse(&lex("enum MyEnum { X, Y }").unwrap()).unwrap()[0],
|
parse(&lex("enum MyEnum { X, Y }").unwrap()).unwrap()[0],
|
||||||
Statement::EnumDeclaration(
|
Statement::EnumDeclaration(
|
||||||
EnumDeclaration {
|
EnumDeclaration {
|
||||||
name: Identifier::new("MyEnum").with_position((0, 0)),
|
name: Identifier::new("MyEnum").with_position((5, 11)),
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
variants: vec![
|
variants: vec![
|
||||||
EnumVariant {
|
EnumVariant {
|
||||||
name: Identifier::new("X").with_position((0, 0)),
|
name: Identifier::new("X").with_position((14, 15)),
|
||||||
content: None
|
content: None
|
||||||
},
|
},
|
||||||
EnumVariant {
|
EnumVariant {
|
||||||
name: Identifier::new("Y").with_position((0, 0)),
|
name: Identifier::new("Y").with_position((17, 18)),
|
||||||
content: None
|
content: None
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
.with_position((0, 0))
|
.with_position((0, 20))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -79,25 +79,29 @@ fn enum_with_contents() {
|
|||||||
parse(&lex("enum MyEnum { X(str, int), Y(int) }").unwrap()).unwrap()[0],
|
parse(&lex("enum MyEnum { X(str, int), Y(int) }").unwrap()).unwrap()[0],
|
||||||
Statement::EnumDeclaration(
|
Statement::EnumDeclaration(
|
||||||
EnumDeclaration {
|
EnumDeclaration {
|
||||||
name: Identifier::new("MyEnum").with_position((0, 0)),
|
name: Identifier::new("MyEnum").with_position((5, 11)),
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
variants: vec![
|
variants: vec![
|
||||||
EnumVariant {
|
EnumVariant {
|
||||||
name: Identifier::new("X").with_position((0, 0)),
|
name: Identifier::new("X").with_position((14, 15)),
|
||||||
content: Some(vec![
|
content: Some(vec![
|
||||||
TypeConstructor::Raw(Type::String.with_position((0, 0))),
|
TypeConstructor::Raw(
|
||||||
TypeConstructor::Raw(Type::Integer.with_position((0, 0))),
|
RawTypeConstructor::String.with_position((16, 19))
|
||||||
|
),
|
||||||
|
TypeConstructor::Raw(
|
||||||
|
RawTypeConstructor::Integer.with_position((21, 24))
|
||||||
|
),
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
EnumVariant {
|
EnumVariant {
|
||||||
name: Identifier::new("Y").with_position((0, 0)),
|
name: Identifier::new("Y").with_position((27, 28)),
|
||||||
content: Some(vec![TypeConstructor::Raw(
|
content: Some(vec![TypeConstructor::Raw(
|
||||||
Type::Integer.with_position((0, 0))
|
RawTypeConstructor::Integer.with_position((29, 32))
|
||||||
),])
|
),])
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
.with_position((0, 0))
|
.with_position((0, 35))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -108,29 +112,33 @@ fn enum_with_type_parameters() {
|
|||||||
parse(&lex("enum MyEnum <T, U> { X(T), Y(U) }").unwrap()).unwrap()[0],
|
parse(&lex("enum MyEnum <T, U> { X(T), Y(U) }").unwrap()).unwrap()[0],
|
||||||
Statement::EnumDeclaration(
|
Statement::EnumDeclaration(
|
||||||
EnumDeclaration {
|
EnumDeclaration {
|
||||||
name: Identifier::new("MyEnum").with_position((0, 0)),
|
name: Identifier::new("MyEnum").with_position((5, 11)),
|
||||||
type_parameters: Some(vec![
|
type_parameters: Some(vec![
|
||||||
Identifier::new("T").with_position((0, 0)),
|
Identifier::new("T").with_position((13, 14)),
|
||||||
Identifier::new("U").with_position((0, 0))
|
Identifier::new("U").with_position((16, 17))
|
||||||
]),
|
]),
|
||||||
variants: vec![
|
variants: vec![
|
||||||
EnumVariant {
|
EnumVariant {
|
||||||
name: Identifier::new("X").with_position((0, 0)),
|
name: Identifier::new("X").with_position((21, 22)),
|
||||||
content: Some(vec![TypeConstructor::Raw(
|
content: Some(vec![TypeConstructor::Invokation(
|
||||||
Type::Generic {
|
TypeInvokationConstructor {
|
||||||
identifier: Identifier::new("T"),
|
identifier: Identifier::new("T").with_position((23, 24)),
|
||||||
concrete_type: None
|
type_arguments: None
|
||||||
}
|
}
|
||||||
.with_position((0, 0))
|
|
||||||
)])
|
)])
|
||||||
},
|
},
|
||||||
EnumVariant {
|
EnumVariant {
|
||||||
name: todo!(),
|
name: Identifier::new("Y").with_position((27, 28)),
|
||||||
content: todo!()
|
content: Some(vec![TypeConstructor::Invokation(
|
||||||
|
TypeInvokationConstructor {
|
||||||
|
identifier: Identifier::new("U").with_position((29, 30)),
|
||||||
|
type_arguments: None
|
||||||
}
|
}
|
||||||
|
)])
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
.with_position((0, 0))
|
.with_position((0, 33))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -209,7 +217,7 @@ fn r#as() {
|
|||||||
Statement::Expression(Expression::As(
|
Statement::Expression(Expression::As(
|
||||||
Box::new(As::new(
|
Box::new(As::new(
|
||||||
Expression::Value(ValueNode::Integer(1).with_position((0, 1))),
|
Expression::Value(ValueNode::Integer(1).with_position((0, 1))),
|
||||||
TypeConstructor::Raw(Type::String.with_position((5, 8)))
|
TypeConstructor::Raw(RawTypeConstructor::String.with_position((5, 8)))
|
||||||
))
|
))
|
||||||
.with_position((0, 8))
|
.with_position((0, 8))
|
||||||
))
|
))
|
||||||
@ -352,7 +360,9 @@ fn boolean_type() {
|
|||||||
Statement::Assignment(
|
Statement::Assignment(
|
||||||
Assignment::new(
|
Assignment::new(
|
||||||
Identifier::new("foobar").with_position((0, 6)),
|
Identifier::new("foobar").with_position((0, 6)),
|
||||||
Some(TypeConstructor::Raw(Type::Boolean.with_position((9, 13)))),
|
Some(TypeConstructor::Raw(
|
||||||
|
RawTypeConstructor::Boolean.with_position((9, 13))
|
||||||
|
)),
|
||||||
AssignmentOperator::Assign,
|
AssignmentOperator::Assign,
|
||||||
Statement::Expression(Expression::Value(
|
Statement::Expression(Expression::Value(
|
||||||
ValueNode::Boolean(true).with_position((16, 20))
|
ValueNode::Boolean(true).with_position((16, 20))
|
||||||
@ -374,7 +384,7 @@ fn list_type() {
|
|||||||
ListTypeConstructor {
|
ListTypeConstructor {
|
||||||
length: 2,
|
length: 2,
|
||||||
item_type: Box::new(TypeConstructor::Raw(
|
item_type: Box::new(TypeConstructor::Raw(
|
||||||
Type::Integer.with_position((9, 12))
|
RawTypeConstructor::Integer.with_position((9, 12))
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
.with_position((8, 16))
|
.with_position((8, 16))
|
||||||
@ -397,7 +407,9 @@ fn list_of_type() {
|
|||||||
Assignment::new(
|
Assignment::new(
|
||||||
Identifier::new("foobar").with_position((0, 6)),
|
Identifier::new("foobar").with_position((0, 6)),
|
||||||
Some(TypeConstructor::ListOf(
|
Some(TypeConstructor::ListOf(
|
||||||
Box::new(TypeConstructor::Raw(Type::Boolean.with_position((10, 14))))
|
Box::new(TypeConstructor::Raw(
|
||||||
|
RawTypeConstructor::Boolean.with_position((10, 14))
|
||||||
|
))
|
||||||
.with_position((9, 15))
|
.with_position((9, 15))
|
||||||
)),
|
)),
|
||||||
AssignmentOperator::Assign,
|
AssignmentOperator::Assign,
|
||||||
@ -419,24 +431,24 @@ fn function_type() {
|
|||||||
parse(&lex("type Foo = fn |T| (int) -> T").unwrap()).unwrap()[0],
|
parse(&lex("type Foo = fn |T| (int) -> T").unwrap()).unwrap()[0],
|
||||||
Statement::TypeAlias(
|
Statement::TypeAlias(
|
||||||
TypeAlias::new(
|
TypeAlias::new(
|
||||||
Identifier::new("Foo").with_position((0, 0)),
|
Identifier::new("Foo").with_position((5, 8)),
|
||||||
TypeConstructor::Function(
|
TypeConstructor::Function(
|
||||||
FunctionTypeConstructor {
|
FunctionTypeConstructor {
|
||||||
type_parameters: Some(vec![Identifier::new("T").with_position((0, 0))]),
|
type_parameters: Some(vec![Identifier::new("T").with_position((15, 16))]),
|
||||||
value_parameters: vec![TypeConstructor::Raw(
|
value_parameters: vec![TypeConstructor::Raw(
|
||||||
Type::Integer.with_position((0, 0))
|
RawTypeConstructor::Integer.with_position((19, 22))
|
||||||
)],
|
)],
|
||||||
return_type: Box::new(TypeConstructor::Invokation(
|
return_type: Box::new(TypeConstructor::Invokation(
|
||||||
TypeInvokationConstructor {
|
TypeInvokationConstructor {
|
||||||
identifier: Identifier::new("T").with_position((0, 0)),
|
identifier: Identifier::new("T").with_position((27, 28)),
|
||||||
type_arguments: None
|
type_arguments: None
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
.with_position((0, 0))
|
.with_position((11, 28))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.with_position((0, 0))
|
.with_position((0, 28))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -464,7 +476,7 @@ fn function_call_with_type_arguments() {
|
|||||||
FunctionCall::new(
|
FunctionCall::new(
|
||||||
Expression::Identifier(Identifier::new("foobar").with_position((0, 6))),
|
Expression::Identifier(Identifier::new("foobar").with_position((0, 6))),
|
||||||
Some(vec![TypeConstructor::Raw(
|
Some(vec![TypeConstructor::Raw(
|
||||||
Type::String.with_position((9, 12))
|
RawTypeConstructor::String.with_position((9, 12))
|
||||||
)]),
|
)]),
|
||||||
vec![Expression::Value(
|
vec![Expression::Value(
|
||||||
ValueNode::String("hi".to_string()).with_position((16, 20))
|
ValueNode::String("hi".to_string()).with_position((16, 20))
|
||||||
@ -493,7 +505,9 @@ fn function() {
|
|||||||
ValueNode::Function {
|
ValueNode::Function {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: vec![],
|
value_parameters: vec![],
|
||||||
return_type: TypeConstructor::Raw(Type::Integer.with_position((9, 12))),
|
return_type: TypeConstructor::Raw(
|
||||||
|
RawTypeConstructor::Integer.with_position((9, 12))
|
||||||
|
),
|
||||||
body: Block::new(vec![Statement::Expression(Expression::Value(
|
body: Block::new(vec![Statement::Expression(Expression::Value(
|
||||||
ValueNode::Integer(0).with_position((15, 16))
|
ValueNode::Integer(0).with_position((15, 16))
|
||||||
))])
|
))])
|
||||||
@ -510,9 +524,11 @@ fn function() {
|
|||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: vec![(
|
value_parameters: vec![(
|
||||||
Identifier::new("x"),
|
Identifier::new("x"),
|
||||||
TypeConstructor::Raw(Type::Integer.with_position((7, 10)))
|
TypeConstructor::Raw(RawTypeConstructor::Integer.with_position((7, 10)))
|
||||||
)],
|
)],
|
||||||
return_type: TypeConstructor::Raw(Type::Integer.with_position((15, 18))),
|
return_type: TypeConstructor::Raw(
|
||||||
|
RawTypeConstructor::Integer.with_position((15, 18))
|
||||||
|
),
|
||||||
body: Block::new(vec![Statement::Expression(Expression::Identifier(
|
body: Block::new(vec![Statement::Expression(Expression::Identifier(
|
||||||
Identifier::new("x").with_position((21, 22))
|
Identifier::new("x").with_position((21, 22))
|
||||||
))])
|
))])
|
||||||
@ -832,7 +848,9 @@ fn assignment_with_type() {
|
|||||||
Statement::Assignment(
|
Statement::Assignment(
|
||||||
Assignment::new(
|
Assignment::new(
|
||||||
Identifier::new("foobar").with_position((0, 6)),
|
Identifier::new("foobar").with_position((0, 6)),
|
||||||
Some(TypeConstructor::Raw(Type::Integer.with_position((8, 11)))),
|
Some(TypeConstructor::Raw(
|
||||||
|
RawTypeConstructor::Integer.with_position((8, 11))
|
||||||
|
)),
|
||||||
AssignmentOperator::Assign,
|
AssignmentOperator::Assign,
|
||||||
Statement::Expression(Expression::Value(
|
Statement::Expression(Expression::Value(
|
||||||
ValueNode::Integer(1).with_position((14, 15))
|
ValueNode::Integer(1).with_position((14, 15))
|
||||||
|
@ -6,7 +6,7 @@ fn simple_enum() {
|
|||||||
interpret(
|
interpret(
|
||||||
"test",
|
"test",
|
||||||
"
|
"
|
||||||
type FooBar = enum {
|
enum FooBar {
|
||||||
Foo,
|
Foo,
|
||||||
Bar,
|
Bar,
|
||||||
}
|
}
|
||||||
@ -28,7 +28,7 @@ fn big_enum() {
|
|||||||
interpret(
|
interpret(
|
||||||
"test",
|
"test",
|
||||||
"
|
"
|
||||||
type FooBarBaz = enum |T, U, V| {
|
enum FooBarBaz <T, U, V> {
|
||||||
Foo(T),
|
Foo(T),
|
||||||
Bar(U),
|
Bar(U),
|
||||||
Baz(V),
|
Baz(V),
|
||||||
|
Loading…
Reference in New Issue
Block a user