Assign statements ir and fixing some things.
This commit is contained in:
parent
7e613b1b90
commit
5a123419bd
@ -114,7 +114,7 @@ fn compile_expression(
|
||||
let mut ir_builder = IrBuilder::new();
|
||||
let entry_block_id = ir_builder.new_block();
|
||||
|
||||
let maybe_ir_expression = expression.to_ir(&mut ir_builder, &symbol_table);
|
||||
let maybe_ir_expression = expression.to_ir_expression(&mut ir_builder, &symbol_table);
|
||||
|
||||
// if Some, return the value
|
||||
ir_builder
|
||||
|
||||
@ -83,11 +83,11 @@ impl AddExpression {
|
||||
pub fn to_ir(&self, builder: &mut IrBuilder, symbol_table: &SymbolTable) -> IrAdd {
|
||||
let lhs_ir_expression = self
|
||||
.lhs
|
||||
.to_ir(builder, symbol_table)
|
||||
.to_ir_expression(builder, symbol_table)
|
||||
.expect("Attempt to add non-expression");
|
||||
let rhs_ir_expression = self
|
||||
.rhs
|
||||
.to_ir(builder, symbol_table)
|
||||
.to_ir_expression(builder, symbol_table)
|
||||
.expect("Attempt to add non-expression");
|
||||
IrAdd::new(lhs_ir_expression, rhs_ir_expression)
|
||||
}
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
use crate::ast::expression::Expression;
|
||||
use crate::ast::ir_builder::IrBuilder;
|
||||
use crate::ast::ir_util::get_or_init_mut_field_pointer_variable;
|
||||
use crate::diagnostic::{Diagnostic, SecondaryLabel};
|
||||
use crate::error_codes::{ASSIGN_LHS_IMMUTABLE, ASSIGN_MISMATCHED_TYPES, ASSIGN_NO_L_VALUE};
|
||||
use crate::ir::ir_assign::IrAssign;
|
||||
use crate::ir::ir_set_field::IrSetField;
|
||||
use crate::ir::ir_statement::IrStatement;
|
||||
use crate::symbol::expressible_symbol::ExpressibleSymbol;
|
||||
use crate::symbol_table::SymbolTable;
|
||||
|
||||
pub struct AssignStatement {
|
||||
@ -157,6 +163,38 @@ impl AssignStatement {
|
||||
Err(diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_ir(&self, builder: &mut IrBuilder, symbol_table: &SymbolTable) {
|
||||
let destination_symbol = match &*self.destination {
|
||||
Expression::Identifier(identifier) => identifier.expressible_symbol(),
|
||||
_ => unreachable!("Destination must be a mutable L value"),
|
||||
};
|
||||
|
||||
let ir_statement = match destination_symbol {
|
||||
ExpressibleSymbol::Field(field_symbol) => {
|
||||
let mut_field_pointer_variable =
|
||||
get_or_init_mut_field_pointer_variable(builder, field_symbol).clone();
|
||||
let ir_set_field = IrSetField::new(
|
||||
&mut_field_pointer_variable,
|
||||
self.value
|
||||
.to_ir_expression(builder, symbol_table)
|
||||
.expect("Attempt to convert non-value to value"),
|
||||
);
|
||||
IrStatement::SetField(ir_set_field)
|
||||
}
|
||||
ExpressibleSymbol::Variable(variable_symbol) => {
|
||||
let vr_variable = variable_symbol.borrow().vr_variable().clone();
|
||||
let ir_assign = IrAssign::new(
|
||||
vr_variable,
|
||||
self.value.to_ir_operation(builder, symbol_table),
|
||||
);
|
||||
IrStatement::Assign(ir_assign)
|
||||
}
|
||||
_ => unreachable!("Destination must be a mutable L value"),
|
||||
};
|
||||
|
||||
builder.current_block_mut().add_statement(ir_statement);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -5,7 +5,6 @@ use crate::diagnostic::Diagnostic;
|
||||
use crate::ir::ir_call::IrCall;
|
||||
use crate::ir::ir_expression::IrExpression;
|
||||
use crate::source_range::SourceRange;
|
||||
use crate::symbol::Symbol;
|
||||
use crate::symbol::callable_symbol::CallableSymbol;
|
||||
use crate::symbol::expressible_symbol::ExpressibleSymbol;
|
||||
use crate::symbol_table::SymbolTable;
|
||||
@ -176,7 +175,7 @@ impl Call {
|
||||
let arguments: Vec<IrExpression> = self
|
||||
.arguments
|
||||
.iter()
|
||||
.map(|argument| argument.to_ir(builder, symbol_table))
|
||||
.map(|argument| argument.to_ir_expression(builder, symbol_table))
|
||||
.inspect(|expression| {
|
||||
if expression.is_none() {
|
||||
panic!("Attempt to pass non-expression")
|
||||
|
||||
@ -200,13 +200,19 @@ impl Constructor {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, parameter)| {
|
||||
let parameter_symbol = parameter.parameter_symbol().borrow();
|
||||
let mut parameter_symbol = parameter.parameter_symbol().borrow_mut();
|
||||
let offset = (parameters_count as isize).neg() + i as isize;
|
||||
Rc::new(IrParameter::new(
|
||||
let ir_parameter = Rc::new(IrParameter::new(
|
||||
parameter_symbol.declared_name(),
|
||||
parameter_symbol.type_info().clone(),
|
||||
offset,
|
||||
))
|
||||
));
|
||||
|
||||
// make sure to save ir_parameter to symbol so others can access it
|
||||
let to_save = ir_parameter.clone();
|
||||
parameter_symbol.set_ir_parameter(to_save);
|
||||
|
||||
ir_parameter
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@ -219,6 +225,11 @@ impl Constructor {
|
||||
&TypeInfo::ClassInstance(class_symbol.clone()),
|
||||
);
|
||||
let self_variable = Rc::new(RefCell::new(alloc_assign_destination));
|
||||
|
||||
// save self variable so statements can assign stuff to self's fields
|
||||
ir_builder
|
||||
.set_self_parameter_or_variable(IrParameterOrVariable::Variable(self_variable.clone()));
|
||||
|
||||
let alloc_assign = IrAssign::new(
|
||||
self_variable.clone(),
|
||||
IrOperation::Allocate(IrAllocate::new(class_symbol.borrow().declared_name_owned())),
|
||||
@ -252,16 +263,18 @@ impl Constructor {
|
||||
// save the mut ref to the builder for other uses if needed
|
||||
ir_builder
|
||||
.field_mut_pointer_variables_mut()
|
||||
.insert(field_mut_ref_variable_name.clone(), field_mut_ref_variable);
|
||||
.insert(field.declared_name_owned(), field_mut_ref_variable); // n.b. field name, not t var name
|
||||
|
||||
// now write the initializer result to the field
|
||||
let field_mut_ref_variable = ir_builder
|
||||
.field_mut_pointer_variables()
|
||||
.get(&field_mut_ref_variable_name)
|
||||
.get(field.declared_name())
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
let ir_expression = initializer.to_ir(&mut ir_builder, symbol_table).unwrap();
|
||||
let ir_expression = initializer
|
||||
.to_ir_expression(&mut ir_builder, symbol_table)
|
||||
.unwrap();
|
||||
let ir_set_field = IrSetField::new(
|
||||
&field_mut_ref_variable, // dumb that we clone it and then ref it
|
||||
ir_expression,
|
||||
@ -272,6 +285,11 @@ impl Constructor {
|
||||
}
|
||||
}
|
||||
|
||||
// do "declared" statements of constructor
|
||||
for statement in &self.statements {
|
||||
statement.to_ir(&mut ir_builder, symbol_table, false);
|
||||
}
|
||||
|
||||
// return complete self object
|
||||
let ir_return_statement = IrStatement::Return(IrReturn::new(Some(IrExpression::Variable(
|
||||
self_variable.clone(),
|
||||
|
||||
@ -111,7 +111,38 @@ impl Expression {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_ir(
|
||||
pub fn to_ir_operation(
|
||||
&self,
|
||||
builder: &mut IrBuilder,
|
||||
symbol_table: &SymbolTable,
|
||||
) -> IrOperation {
|
||||
match self {
|
||||
Expression::Call(call) => IrOperation::Call(call.to_ir(builder, symbol_table)),
|
||||
Expression::Integer(integer_literal) => {
|
||||
IrOperation::Load(IrExpression::Int(integer_literal.value()))
|
||||
}
|
||||
Expression::Double(double_literal) => {
|
||||
IrOperation::Load(IrExpression::Double(double_literal.value()))
|
||||
}
|
||||
Expression::String(string_literal) => {
|
||||
IrOperation::Load(IrExpression::String(string_literal.content().into()))
|
||||
}
|
||||
Expression::Identifier(identifier) => {
|
||||
IrOperation::Load(identifier.expressible_symbol().ir_expression(builder))
|
||||
}
|
||||
Expression::Add(additive_expression) => {
|
||||
IrOperation::Add(additive_expression.to_ir(builder, symbol_table))
|
||||
}
|
||||
Expression::Subtract(subtract_expression) => {
|
||||
IrOperation::Subtract(subtract_expression.to_ir_subtract(builder, symbol_table))
|
||||
}
|
||||
Expression::Negative(negative_expression) => {
|
||||
IrOperation::Load(negative_expression.to_ir(builder, symbol_table))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_ir_expression(
|
||||
&self,
|
||||
builder: &mut IrBuilder,
|
||||
symbol_table: &SymbolTable,
|
||||
|
||||
@ -62,7 +62,7 @@ impl ExpressionStatement {
|
||||
symbol_table: &SymbolTable,
|
||||
should_return_value: bool,
|
||||
) {
|
||||
let ir_expression = self.expression.to_ir(builder, symbol_table);
|
||||
let ir_expression = self.expression.to_ir_expression(builder, symbol_table);
|
||||
if ir_expression.is_some() && should_return_value {
|
||||
builder
|
||||
.current_block_mut()
|
||||
|
||||
@ -42,6 +42,10 @@ impl Field {
|
||||
&self.declared_name
|
||||
}
|
||||
|
||||
pub fn declared_name_owned(&self) -> Rc<str> {
|
||||
self.declared_name.clone()
|
||||
}
|
||||
|
||||
pub fn declared_name_source_range(&self) -> &SourceRange {
|
||||
&self.declared_name_source_range
|
||||
}
|
||||
|
||||
83
dmc-lib/src/ast/ir_util.rs
Normal file
83
dmc-lib/src/ast/ir_util.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use crate::ast::ir_builder::IrBuilder;
|
||||
use crate::ir::ir_assign::IrAssign;
|
||||
use crate::ir::ir_get_field_ref::IrGetFieldRef;
|
||||
use crate::ir::ir_get_field_ref_mut::IrGetFieldRefMut;
|
||||
use crate::ir::ir_operation::IrOperation;
|
||||
use crate::ir::ir_statement::IrStatement;
|
||||
use crate::ir::ir_variable::IrVariable;
|
||||
use crate::symbol::Symbol;
|
||||
use crate::symbol::field_symbol::FieldSymbol;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn get_or_init_field_pointer_variable<'a>(
|
||||
builder: &'a mut IrBuilder,
|
||||
field_symbol: &Rc<RefCell<FieldSymbol>>,
|
||||
) -> &'a Rc<RefCell<IrVariable>> {
|
||||
// This following should work because blocks are flat in the ir; if a variable is defined in the
|
||||
// ir block from this point forward, it's available to all subsequent blocks.
|
||||
if !builder
|
||||
.field_pointer_variables()
|
||||
.contains_key(field_symbol.borrow().declared_name())
|
||||
{
|
||||
let field_ref_variable = IrVariable::new_vr(
|
||||
builder.new_t_var().into(),
|
||||
builder.current_block().id(),
|
||||
field_symbol.borrow().type_info(),
|
||||
);
|
||||
let as_rc = Rc::new(RefCell::new(field_ref_variable));
|
||||
let to_insert = as_rc.clone();
|
||||
let self_parameter_or_variable = builder.self_parameter_or_variable().clone();
|
||||
builder
|
||||
.current_block_mut()
|
||||
.add_statement(IrStatement::Assign(IrAssign::new(
|
||||
as_rc,
|
||||
IrOperation::GetFieldRef(IrGetFieldRef::new(
|
||||
self_parameter_or_variable.clone(),
|
||||
field_symbol.borrow().field_index(),
|
||||
)),
|
||||
)));
|
||||
builder
|
||||
.field_pointer_variables_mut()
|
||||
.insert(field_symbol.borrow().declared_name_owned(), to_insert);
|
||||
}
|
||||
builder
|
||||
.field_pointer_variables()
|
||||
.get(field_symbol.borrow().declared_name())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn get_or_init_mut_field_pointer_variable<'a>(
|
||||
builder: &'a mut IrBuilder,
|
||||
field_symbol: &Rc<RefCell<FieldSymbol>>,
|
||||
) -> &'a Rc<RefCell<IrVariable>> {
|
||||
if !builder
|
||||
.field_mut_pointer_variables()
|
||||
.contains_key(field_symbol.borrow().declared_name())
|
||||
{
|
||||
let mut_field_pointer_variable = IrVariable::new_vr(
|
||||
builder.new_t_var().into(),
|
||||
builder.current_block().id(),
|
||||
field_symbol.borrow().type_info(),
|
||||
);
|
||||
let as_rc = Rc::new(RefCell::new(mut_field_pointer_variable));
|
||||
let to_insert = as_rc.clone();
|
||||
let self_parameter_or_variable = builder.self_parameter_or_variable().clone();
|
||||
builder
|
||||
.current_block_mut()
|
||||
.add_statement(IrStatement::Assign(IrAssign::new(
|
||||
as_rc,
|
||||
IrOperation::GetFieldRefMut(IrGetFieldRefMut::new(
|
||||
self_parameter_or_variable.clone(),
|
||||
field_symbol.borrow().field_index(),
|
||||
)),
|
||||
)));
|
||||
builder
|
||||
.field_mut_pointer_variables_mut()
|
||||
.insert(field_symbol.borrow().declared_name_owned(), to_insert);
|
||||
}
|
||||
builder
|
||||
.field_mut_pointer_variables()
|
||||
.get(field_symbol.borrow().declared_name())
|
||||
.unwrap()
|
||||
}
|
||||
@ -2,8 +2,6 @@ use crate::ast::expression::Expression;
|
||||
use crate::ast::ir_builder::IrBuilder;
|
||||
use crate::diagnostic::Diagnostic;
|
||||
use crate::ir::ir_assign::IrAssign;
|
||||
use crate::ir::ir_expression::IrExpression;
|
||||
use crate::ir::ir_operation::IrOperation;
|
||||
use crate::ir::ir_statement::IrStatement;
|
||||
use crate::ir::ir_variable::IrVariable;
|
||||
use crate::source_range::SourceRange;
|
||||
@ -106,28 +104,7 @@ impl LetStatement {
|
||||
}
|
||||
|
||||
pub fn to_ir(&self, builder: &mut IrBuilder, symbol_table: &SymbolTable) {
|
||||
let init_operation = match self.initializer() {
|
||||
Expression::Call(call) => IrOperation::Call(call.to_ir(builder, symbol_table)),
|
||||
Expression::Integer(integer_literal) => {
|
||||
IrOperation::Load(IrExpression::Int(integer_literal.value()))
|
||||
}
|
||||
Expression::Double(double_literal) => {
|
||||
IrOperation::Load(IrExpression::Double(double_literal.value()))
|
||||
}
|
||||
Expression::String(string_literal) => {
|
||||
IrOperation::Load(IrExpression::String(string_literal.content().into()))
|
||||
}
|
||||
Expression::Identifier(identifier) => {
|
||||
IrOperation::Load(identifier.expressible_symbol().ir_expression(builder))
|
||||
}
|
||||
Expression::Add(additive_expression) => {
|
||||
IrOperation::Add(additive_expression.to_ir(builder, symbol_table))
|
||||
}
|
||||
Expression::Subtract(subtract_expression) => {
|
||||
IrOperation::Subtract(subtract_expression.to_ir_subtract(builder, symbol_table))
|
||||
}
|
||||
Expression::Negative(_) => todo!(),
|
||||
};
|
||||
let init_operation = self.initializer.to_ir_operation(builder, symbol_table);
|
||||
|
||||
let destination_symbol =
|
||||
symbol_table.get_variable_symbol(self.scope_id.unwrap(), &self.declared_name);
|
||||
|
||||
@ -16,6 +16,7 @@ pub mod function;
|
||||
pub mod identifier;
|
||||
pub mod integer_literal;
|
||||
pub mod ir_builder;
|
||||
pub(crate) mod ir_util;
|
||||
pub mod let_statement;
|
||||
pub mod negative_expression;
|
||||
pub mod parameter;
|
||||
|
||||
@ -70,7 +70,7 @@ impl NegativeExpression {
|
||||
pub fn to_ir(&self, builder: &mut IrBuilder, symbol_table: &SymbolTable) -> IrExpression {
|
||||
let operand_as_ir = self
|
||||
.operand
|
||||
.to_ir(builder, symbol_table)
|
||||
.to_ir_expression(builder, symbol_table)
|
||||
.expect("Attempt to negate non-value expression");
|
||||
|
||||
match operand_as_ir {
|
||||
|
||||
@ -66,7 +66,7 @@ impl Statement {
|
||||
expression_statement.to_ir(builder, symbol_table, should_return_value);
|
||||
}
|
||||
Statement::Assign(assign_statement) => {
|
||||
todo!()
|
||||
assign_statement.to_ir(builder, symbol_table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,11 +105,11 @@ impl SubtractExpression {
|
||||
) -> IrSubtract {
|
||||
let lhs = self
|
||||
.lhs
|
||||
.to_ir(builder, symbol_table)
|
||||
.to_ir_expression(builder, symbol_table)
|
||||
.expect("Attempt to subtract non-expression");
|
||||
let rhs = self
|
||||
.rhs
|
||||
.to_ir(builder, symbol_table)
|
||||
.to_ir_expression(builder, symbol_table)
|
||||
.expect("Attempt to subtract non-expression");
|
||||
IrSubtract::new(lhs, rhs)
|
||||
}
|
||||
|
||||
@ -58,12 +58,16 @@ impl VrUser for IrAssign {
|
||||
&mut self,
|
||||
assignments: &HashMap<IrVrVariableDescriptor, usize>,
|
||||
) {
|
||||
let mut borrowed_destination = self.destination.borrow_mut();
|
||||
if let IrVariableDescriptor::VirtualRegister(vr_variable) =
|
||||
borrowed_destination.descriptor_mut()
|
||||
// Have to isolate the following, because when the destination and the value are the same,
|
||||
// we get a BorrowMutError
|
||||
{
|
||||
if assignments.contains_key(vr_variable) {
|
||||
vr_variable.set_assigned_register(assignments[vr_variable]);
|
||||
let mut borrowed_destination = self.destination.borrow_mut();
|
||||
if let IrVariableDescriptor::VirtualRegister(vr_variable) =
|
||||
borrowed_destination.descriptor_mut()
|
||||
{
|
||||
if assignments.contains_key(vr_variable) {
|
||||
vr_variable.set_assigned_register(assignments[vr_variable]);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.initializer.propagate_register_assignments(assignments);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::ast::ir_builder::IrBuilder;
|
||||
use crate::ast::ir_util::get_or_init_field_pointer_variable;
|
||||
use crate::ir::ir_assign::IrAssign;
|
||||
use crate::ir::ir_expression::IrExpression;
|
||||
use crate::ir::ir_get_field_ref::IrGetFieldRef;
|
||||
use crate::ir::ir_operation::IrOperation;
|
||||
use crate::ir::ir_read_field::IrReadField;
|
||||
use crate::ir::ir_statement::IrStatement;
|
||||
@ -79,34 +79,6 @@ impl ExpressibleSymbol {
|
||||
todo!()
|
||||
}
|
||||
ExpressibleSymbol::Field(field_symbol) => {
|
||||
// This following should work because blocks are flat in the ir; if a variable is
|
||||
// defined in the ir block from this point forward, it's available to all subsequent
|
||||
// blocks.
|
||||
if !builder
|
||||
.field_pointer_variables()
|
||||
.contains_key(field_symbol.borrow().declared_name())
|
||||
{
|
||||
let field_ref_variable = IrVariable::new_vr(
|
||||
builder.new_t_var().into(),
|
||||
builder.current_block().id(),
|
||||
field_symbol.borrow().type_info(),
|
||||
);
|
||||
let as_rc = Rc::new(RefCell::new(field_ref_variable));
|
||||
let to_insert = as_rc.clone();
|
||||
let self_parameter_or_variable = builder.self_parameter_or_variable().clone();
|
||||
builder
|
||||
.current_block_mut()
|
||||
.add_statement(IrStatement::Assign(IrAssign::new(
|
||||
as_rc,
|
||||
IrOperation::GetFieldRef(IrGetFieldRef::new(
|
||||
self_parameter_or_variable.clone(),
|
||||
field_symbol.borrow().field_index(),
|
||||
)),
|
||||
)));
|
||||
builder
|
||||
.field_pointer_variables_mut()
|
||||
.insert(field_symbol.borrow().declared_name_owned(), to_insert);
|
||||
}
|
||||
// now we need to read the field into a variable and return an expression pointing
|
||||
// to that variable
|
||||
let read_destination = IrVariable::new_vr(
|
||||
@ -116,11 +88,7 @@ impl ExpressibleSymbol {
|
||||
);
|
||||
let read_destination_as_rc = Rc::new(RefCell::new(read_destination));
|
||||
let ir_read_field = IrReadField::new(
|
||||
builder
|
||||
.field_pointer_variables()
|
||||
.get(field_symbol.borrow().declared_name())
|
||||
.unwrap()
|
||||
.clone(),
|
||||
get_or_init_field_pointer_variable(builder, field_symbol).clone(),
|
||||
);
|
||||
builder
|
||||
.current_block_mut()
|
||||
|
||||
@ -248,6 +248,48 @@ mod e2e_tests {
|
||||
assert_eq!(o.fields().len(), 1);
|
||||
assert_eq!(o.fields()[0].unwrap_int(), 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_assign() {
|
||||
assert_result(
|
||||
"
|
||||
fn assign() -> Int
|
||||
let mut x = 21
|
||||
x = x + x
|
||||
x
|
||||
end
|
||||
",
|
||||
"assign",
|
||||
&vec![],
|
||||
Value::Int(42),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assign_field() {
|
||||
let context = prepare_context(
|
||||
"
|
||||
class Foo
|
||||
mut bar = 21
|
||||
|
||||
ctor(_bar: Int)
|
||||
bar = _bar
|
||||
end
|
||||
end
|
||||
|
||||
fn foo() -> Foo
|
||||
Foo(42)
|
||||
end
|
||||
",
|
||||
);
|
||||
let result = get_result(&context, "foo", &vec![]);
|
||||
assert!(result.is_some());
|
||||
let value = result.unwrap();
|
||||
assert!(matches!(value, Value::Object(_)));
|
||||
let o = value.unwrap_object().borrow();
|
||||
assert_eq!(o.fields().len(), 1);
|
||||
assert_eq!(o.fields()[0].unwrap_int(), 42);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
11
examples/assign_field.dm
Normal file
11
examples/assign_field.dm
Normal file
@ -0,0 +1,11 @@
|
||||
class Foo
|
||||
mut bar = 21
|
||||
|
||||
ctor(_bar: Int)
|
||||
bar = _bar
|
||||
end
|
||||
end
|
||||
|
||||
fn main() -> Foo
|
||||
Foo(42)
|
||||
end
|
||||
Loading…
Reference in New Issue
Block a user