mirror of
https://github.com/KarolS/millfork.git
synced 2025-01-10 20:29:35 +00:00
Better type error reporting
This commit is contained in:
parent
89ff89bc48
commit
d1c0ad6b22
@ -14,11 +14,14 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
|
||||
|
||||
def getExpressionType(ctx: CompilationContext, expr: Expression): Type = AbstractExpressionCompiler.getExpressionType(ctx, expr)
|
||||
|
||||
def assertAllArithmetic(ctx: CompilationContext,expressions: List[Expression]): Unit = {
|
||||
def assertAllArithmetic(ctx: CompilationContext,expressions: List[Expression], booleanHint: String = ""): Unit = {
|
||||
for(e <- expressions) {
|
||||
val typ = getExpressionType(ctx, e)
|
||||
if (!typ.isArithmetic) {
|
||||
ctx.log.error(s"Cannot perform arithmetic operations on type `$typ`", e.position)
|
||||
if (booleanHint != "" && typ.isBoollike) {
|
||||
ctx.log.info(s"Did you mean: $booleanHint")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -36,8 +39,8 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
|
||||
ctx.copy(env = result)
|
||||
}
|
||||
|
||||
def getArithmeticParamMaxSize(ctx: CompilationContext, params: List[Expression]): Int = {
|
||||
assertAllArithmetic(ctx, params)
|
||||
def getArithmeticParamMaxSize(ctx: CompilationContext, params: List[Expression], booleanHint: String = ""): Int = {
|
||||
assertAllArithmetic(ctx, params, booleanHint = booleanHint)
|
||||
params.map(expr => getExpressionType(ctx, expr).size).max
|
||||
}
|
||||
|
||||
@ -159,8 +162,8 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
|
||||
getExpressionType(ctx, param) match {
|
||||
case _: BooleanType =>
|
||||
case FatBooleanType =>
|
||||
case _=>
|
||||
ctx.log.fatal("Parameter should be boolean", param.position)
|
||||
case t =>
|
||||
ctx.log.fatal(s"Parameter has type `${t.name}`, but it should be boolean", param.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -429,6 +432,7 @@ object AbstractExpressionCompiler {
|
||||
}
|
||||
case 3 => env.get[Type]("int24")
|
||||
case 4 => env.get[Type]("int32")
|
||||
case 0 => b
|
||||
case _ => log.error("Adding values bigger than longs", expr.position); env.get[Type]("int32")
|
||||
}
|
||||
case FunctionCallExpression("nonet", _) => w
|
||||
@ -470,14 +474,16 @@ object AbstractExpressionCompiler {
|
||||
case FunctionCallExpression("sin", params) => if (params.size < 2) b else getExpressionTypeImpl(env, log, params(1), loosely)
|
||||
case FunctionCallExpression("cos", params) => if (params.size < 2) b else getExpressionTypeImpl(env, log, params(1), loosely)
|
||||
case FunctionCallExpression("tan", params) => if (params.size < 2) b else getExpressionTypeImpl(env, log, params(1), loosely)
|
||||
case FunctionCallExpression("min" | "max", params) => if (params.isEmpty) b else params.map { e => getExpressionTypeImpl(env, log, e, loosely).size }.max match {
|
||||
case FunctionCallExpression(name@("min" | "max"), params) => if (params.isEmpty) b else params.map { e => getExpressionTypeImpl(env, log, e, loosely).size }.max match {
|
||||
case 1 => b
|
||||
case 2 => w
|
||||
case 0 => log.error(s"Invalid parameters to $name", expr.position); b
|
||||
case n if n >= 3 => env.get[Type]("int" + n * 8)
|
||||
} // TODO: ?
|
||||
case FunctionCallExpression("if", params) => if (params.length < 3) b else params.tail.map { e => getExpressionTypeImpl(env, log, e, loosely).size }.max match {
|
||||
case 1 => b
|
||||
case 2 => w
|
||||
case 0 => log.error(s"Invalid parameters to if", expr.position); b
|
||||
case n if n >= 3 => env.get[Type]("int" + n * 8)
|
||||
} // TODO: ?
|
||||
case FunctionCallExpression("sizeof", params) => env.evalSizeof(params.head).requiredSize match {
|
||||
@ -487,10 +493,11 @@ object AbstractExpressionCompiler {
|
||||
case FunctionCallExpression("%%", params) => params.map { e => getExpressionTypeImpl(env, log, e, loosely).size } match {
|
||||
case List(1, 1) | List(2, 1) => b
|
||||
case List(1, 2) | List(2, 2) => w
|
||||
case List(0, _) | List(_, 0) => b
|
||||
case _ => log.error("Combining values bigger than words", expr.position); w
|
||||
}
|
||||
case FunctionCallExpression("*" | "|" | "&" | "^" | "/", params) => params.map { e => getExpressionTypeImpl(env, log, e, loosely).size }.max match {
|
||||
case 1 => b
|
||||
case 0 | 1 => b
|
||||
case 2 => w
|
||||
case _ => log.error("Combining values bigger than words", expr.position); w
|
||||
}
|
||||
|
@ -174,6 +174,10 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
|
||||
getArithmeticParamMaxSize(ctx, expressions.map(_._2)) match {
|
||||
case 1 => M6809Buitins.compileByteSum(ctx, e, fromScratch = true) ++ targetifyB(ctx, target, isSigned = false)
|
||||
case 2 => M6809Buitins.compileWordSum(ctx, e, fromScratch = true) ++ targetifyD(ctx, target)
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place addition or subtraction of variables larger than 2 bytes is not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case SeparateBytesExpression(hi, lo) =>
|
||||
val h = compile(ctx, hi, MExpressionTarget.A)
|
||||
@ -255,11 +259,19 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
case 1 => M6809MulDiv.compileByteMultiplication(ctx, params, updateDerefX = false) ++ targetifyB(ctx, target, isSigned = false)
|
||||
case 2 => M6809MulDiv.compileWordMultiplication(ctx, params, updateDerefX = false) ++ targetifyD(ctx, target)
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Multiplication of variables larger than 2 bytes is not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "/" =>
|
||||
assertArithmeticBinary(ctx, params) match {
|
||||
case (l, r, 1) => M6809MulDiv.compileByteDivision(ctx, Some(l), r, mod=false) ++ targetifyB(ctx, target, isSigned = false)
|
||||
case (l, r, 2) => M6809MulDiv.compileWordDivision(ctx, Some(l), r, mod=false) ++ targetifyD(ctx, target)
|
||||
case (l, r, 1) => M6809MulDiv.compileByteDivision(ctx, Some(l), r, mod = false) ++ targetifyB(ctx, target, isSigned = false)
|
||||
case (l, r, 2) => M6809MulDiv.compileWordDivision(ctx, Some(l), r, mod = false) ++ targetifyD(ctx, target)
|
||||
case (_, _, 0) => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Division of variables larger than 2 bytes is not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "%%" =>
|
||||
assertArithmeticBinary(ctx, params) match {
|
||||
@ -270,21 +282,37 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
|
||||
} else {
|
||||
M6809MulDiv.compileWordDivision(ctx, Some(l), r, mod=true) ++ targetifyD(ctx, target)
|
||||
}
|
||||
case (_, _, 0) => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Division of variables larger than 2 bytes is not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "&" =>
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
getArithmeticParamMaxSize(ctx, params, booleanHint = "&&") match {
|
||||
case 1 => M6809Buitins.compileByteBitwise(ctx, params, fromScratch = true, ANDB, MathOperator.And, 0xff) ++ targetifyB(ctx, target, isSigned = false)
|
||||
case 2 => M6809Buitins.compileWordBitwise(ctx, params, fromScratch = true, ANDA, ANDB, MathOperator.And, 0xffff) ++ targetifyD(ctx, target)
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place bit operations of variables larger than 2 bytes are not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "|" =>
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
getArithmeticParamMaxSize(ctx, params, booleanHint = "||") match {
|
||||
case 1 => M6809Buitins.compileByteBitwise(ctx, params, fromScratch = true, ORB, MathOperator.Or, 0) ++ targetifyB(ctx, target, isSigned = false)
|
||||
case 2 => M6809Buitins.compileWordBitwise(ctx, params, fromScratch = true, ORA, ORB, MathOperator.Or, 0) ++ targetifyD(ctx, target)
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place bit operations of variables larger than 2 bytes are not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "^" =>
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
case 1 => M6809Buitins.compileByteBitwise(ctx, params, fromScratch = true, EORB, MathOperator.Exor, 0) ++ targetifyB(ctx, target, isSigned = false)
|
||||
case 2 => M6809Buitins.compileWordBitwise(ctx, params, fromScratch = true, EORA, EORB, MathOperator.Exor, 0) ++ targetifyD(ctx, target)
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place bit operations of variables larger than 2 bytes are not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "&&" =>
|
||||
assertBool(ctx, "&&", params)
|
||||
@ -446,12 +474,20 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
|
||||
size match {
|
||||
case 1 => compileAddressToX(ctx, l) ++ M6809MulDiv.compileByteDivision(ctx, None, r, mod=false)
|
||||
case 2 => compileAddressToX(ctx, l) ++ M6809MulDiv.compileWordDivision(ctx, None, r, mod=false)
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Division of variables larger than 2 bytes is not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "%%=" =>
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
size match {
|
||||
case 1 => compileAddressToX(ctx, l) ++ M6809MulDiv.compileByteDivision(ctx, None, r, mod=true)
|
||||
case 2 => compileAddressToX(ctx, l) ++ M6809MulDiv.compileWordDivision(ctx, None, r, mod=true)
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Division of variables larger than 2 bytes is not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "&=" =>
|
||||
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
|
||||
|
@ -1171,6 +1171,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
} else {
|
||||
PseudoregisterBuiltIns.compileWordAdditionViaAX(ctx, exprTypeAndVariable, expr.position, params, decimal = decimal)
|
||||
}
|
||||
case 0 =>
|
||||
Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place addition or subtraction of variables larger than 2 bytes is not supported", expr.position)
|
||||
Nil
|
||||
@ -1382,11 +1384,15 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
case "^^" => ???
|
||||
case "&" =>
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
getArithmeticParamMaxSize(ctx, params, booleanHint = "&&") match {
|
||||
case 1 =>
|
||||
zeroExtend = true
|
||||
BuiltIns.compileBitOps(AND, ctx, params)
|
||||
case 2 => PseudoregisterBuiltIns.compileWordBitOpsToAX(ctx, params, AND)
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place bit operations of variables larger than 2 bytes are not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "*" =>
|
||||
assertSizesForMultiplication(ctx, params, inPlace = false)
|
||||
@ -1397,13 +1403,21 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
case 2 =>
|
||||
//noinspection ZeroIndexToHead
|
||||
PseudoregisterBuiltIns.compileWordMultiplication(ctx, Some(params(0)), params(1), storeInRegLo = false)
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Multiplication of variables larger than 2 bytes are not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "|" =>
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
getArithmeticParamMaxSize(ctx, params, booleanHint = "||") match {
|
||||
case 1 =>
|
||||
zeroExtend = true
|
||||
BuiltIns.compileBitOps(ORA, ctx, params)
|
||||
case 2 => PseudoregisterBuiltIns.compileWordBitOpsToAX(ctx, params, ORA)
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place bit operations of variables larger than 2 bytes are not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "^" =>
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
@ -1411,6 +1425,10 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
zeroExtend = true
|
||||
BuiltIns.compileBitOps(EOR, ctx, params)
|
||||
case 2 => PseudoregisterBuiltIns.compileWordBitOpsToAX(ctx, params, EOR)
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place bit operations of variables larger than 2 bytes are not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case ">>>>" =>
|
||||
val (l, r, size) = assertArithmeticBinary(ctx, params)
|
||||
@ -1421,7 +1439,10 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
case 1 =>
|
||||
zeroExtend = true
|
||||
BuiltIns.compileShiftOps(LSR, ctx, l ,r)
|
||||
case _ => ???
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place shifts of variables larger than 2 bytes is not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "<<" =>
|
||||
val (l, r, size) = assertArithmeticBinary(ctx, params)
|
||||
@ -1431,6 +1452,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
BuiltIns.compileShiftOps(ASL, ctx, l, r)
|
||||
case 2 =>
|
||||
BuiltIns.maybeCompileShiftFromByteToWord(ctx, l, r, left = true).getOrElse(PseudoregisterBuiltIns.compileWordShiftOps(left = true, ctx, l, r))
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Long shift ops not supported", l.position)
|
||||
Nil
|
||||
@ -1443,6 +1465,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
BuiltIns.compileShiftOps(LSR, ctx, l, r)
|
||||
case 2 =>
|
||||
BuiltIns.maybeCompileShiftFromByteToWord(ctx, l, r, left = false).getOrElse(PseudoregisterBuiltIns.compileWordShiftOps(left = false, ctx, l, r))
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Long shift ops not supported", l.position)
|
||||
Nil
|
||||
@ -1643,7 +1666,10 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
} else {
|
||||
compileAssignment(ctx, FunctionCallExpression("/", List(l, r)).pos(f.position), l)
|
||||
}
|
||||
case _ => ctx.log.fatal("Oops")
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Division of variables larger than 2 bytes is not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "/" | "%%" =>
|
||||
assertSizesForDivision(ctx, params, inPlace = false)
|
||||
@ -1654,6 +1680,10 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
BuiltIns.compileUnsignedByteDivision(ctx, l, r, f.functionName == "%%")
|
||||
case 2 =>
|
||||
BuiltIns.compileUnsignedWordByByteDivision(ctx, l, r, f.functionName == "%%")
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Division of variables larger than 2 bytes is not supported", expr.position)
|
||||
Nil
|
||||
}
|
||||
case "*'=" =>
|
||||
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
|
||||
|
@ -559,6 +559,10 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
getArithmeticParamMaxSize(ctx, params.map(_._2)) match {
|
||||
case 1 => targetifyA(ctx, target, ZBuiltIns.compile8BitSum(ctx, params, decimal), isSigned = false)
|
||||
case 2 => targetifyHL(ctx, target, ZBuiltIns.compile16BitSum(ctx, params, decimal))
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place addition or subtraction of variables larger than 2 bytes is not supported", expression.position)
|
||||
Nil
|
||||
}
|
||||
case SeparateBytesExpression(h, l) =>
|
||||
val hi = compileToA(ctx, h)
|
||||
@ -890,9 +894,13 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
case "^^" => ???
|
||||
|
||||
case "&" =>
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
getArithmeticParamMaxSize(ctx, params, booleanHint = "&&") match {
|
||||
case 1 => targetifyA(ctx, target, ZBuiltIns.compile8BitOperation(ctx, AND, params), isSigned = false)
|
||||
case 2 => targetifyHL(ctx, target, ZBuiltIns.compile16BitOperation(ctx, AND, params))
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place bit operations of variables larger than 2 bytes are not supported", expression.position)
|
||||
Nil
|
||||
}
|
||||
case "*" =>
|
||||
assertSizesForMultiplication(ctx, params, inPlace = false)
|
||||
@ -904,14 +912,22 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
targetifyHL(ctx, target, Z80Multiply.compile16BitMultiplyToHL(ctx, params(0), params(1)))
|
||||
}
|
||||
case "|" =>
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
getArithmeticParamMaxSize(ctx, params, booleanHint = "||") match {
|
||||
case 1 => targetifyA(ctx, target, ZBuiltIns.compile8BitOperation(ctx, OR, params), isSigned = false)
|
||||
case 2 => targetifyHL(ctx, target, ZBuiltIns.compile16BitOperation(ctx, OR, params))
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place bit operations of variables larger than 2 bytes are not supported", expression.position)
|
||||
Nil
|
||||
}
|
||||
case "^" =>
|
||||
getArithmeticParamMaxSize(ctx, params) match {
|
||||
case 1 => targetifyA(ctx, target, ZBuiltIns.compile8BitOperation(ctx, XOR, params), isSigned = false)
|
||||
case 2 => targetifyHL(ctx, target, ZBuiltIns.compile16BitOperation(ctx, XOR, params))
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Non-in-place bit operations of variables larger than 2 bytes are not supported", expression.position)
|
||||
Nil
|
||||
}
|
||||
case ">>>>" =>
|
||||
val (l, r, size) = assertArithmeticBinary(ctx, params)
|
||||
@ -1154,6 +1170,10 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Division of variables larger than 2 bytes is not supported", expression.position)
|
||||
Nil
|
||||
}
|
||||
case "/" | "%%" =>
|
||||
assertSizesForDivision(ctx, params, inPlace = false)
|
||||
@ -1169,6 +1189,10 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
} else {
|
||||
targetifyHL(ctx, target, Z80Multiply.compileUnsignedWordDivision(ctx, Right(l), r, modulo, rhsWord))
|
||||
}
|
||||
case 0 => Nil
|
||||
case _ =>
|
||||
ctx.log.error("Division of variables larger than 2 bytes is not supported", expression.position)
|
||||
Nil
|
||||
}
|
||||
case "*'=" =>
|
||||
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package millfork.test
|
||||
|
||||
import millfork.Cpu
|
||||
import millfork.test.emu.{EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCrossPlatformRun, EmuUnoptimizedRun}
|
||||
import millfork.test.emu.{EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCrossPlatformRun, EmuUnoptimizedRun, ShouldNotCompile}
|
||||
import org.scalatest.{FunSuite, Matchers}
|
||||
|
||||
/**
|
||||
@ -264,4 +264,28 @@ class BooleanSuite extends FunSuite with Matchers {
|
||||
m.readByte(0xc000) should equal(code.count(_ == '↑'))
|
||||
}
|
||||
}
|
||||
|
||||
test("Booleans should not work arithmetically") {
|
||||
ShouldNotCompile(
|
||||
"""
|
||||
|byte b
|
||||
|void main() {
|
||||
| b += b == 1
|
||||
|}
|
||||
|""".stripMargin)
|
||||
ShouldNotCompile(
|
||||
"""
|
||||
|byte b
|
||||
|void main() {
|
||||
| b = (b == 1) + (b == 1)
|
||||
|}
|
||||
|""".stripMargin)
|
||||
ShouldNotCompile(
|
||||
"""
|
||||
|byte b
|
||||
|void main() {
|
||||
| b = (b == 1) | (b == 1)
|
||||
|}
|
||||
|""".stripMargin)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user