1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-12 22:29:33 +00:00

Better type error reporting

This commit is contained in:
Karol Stasiak 2020-07-31 17:50:10 +02:00
parent 89ff89bc48
commit d1c0ad6b22
5 changed files with 139 additions and 18 deletions

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)
}
}