better error handling of invalid number casts

This commit is contained in:
Irmen de Jong 2022-01-07 22:12:13 +01:00
parent 749ad700d8
commit 17694c1d01
4 changed files with 132 additions and 19 deletions

View File

@ -25,7 +25,7 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
&& declConstValue.type != decl.datatype) {
// avoid silent float roundings
if(decl.datatype in IntegerDatatypes && declConstValue.type==DataType.FLOAT) {
errors.err("refused silent rounding of float to avoid loss of precision", decl.value!!.position)
errors.err("refused rounding of float to avoid loss of precision", decl.value!!.position)
} else {
// cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype)

View File

@ -485,19 +485,11 @@ internal class AstChecker(private val program: Program,
}
}
// target type check is already done at the Assignment:
// val targetDt = assignTarget.inferType(program, assignment).typeOrElse(DataType.STR)
// if(targetDt in IterableDatatypes)
// errors.err("cannot assign to a string or array type", assignTarget.position)
if (assignment is Assignment) {
val targetDatatype = assignTarget.inferType(program)
if (targetDatatype.isKnown) {
val constVal = assignment.value.constValue(program)
if (constVal != null) {
checkValueTypeAndRange(targetDatatype.getOr(DataType.BYTE), constVal)
} else {
if(constVal==null) {
val sourceDatatype = assignment.value.inferType(program)
if (sourceDatatype.isUnknown) {
if (assignment.value !is FunctionCallExpression)
@ -897,6 +889,9 @@ internal class AstChecker(private val program: Program,
if(!typecast.expression.inferType(program).isKnown)
errors.err("this expression doesn't return a value", typecast.expression.position)
if(typecast.expression is NumericLiteralValue)
errors.err("can't cast the value to the requested target type", typecast.expression.position)
super.visit(typecast)
}

View File

@ -0,0 +1,114 @@
package prog8tests
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.Pipe
import prog8.codegen.target.C64Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
class TestTypecasts: FunSpec({
test("correct typecasts") {
val text = """
%import floats
main {
sub start() {
float @shared fl = 3.456
uword @shared uw = 5555
byte @shared bb = -44
bb = uw as byte
uw = bb as uword
fl = uw as float
fl = bb as float
bb = fl as byte
uw = fl as uword
}
}
"""
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
result.program.entrypoint.statements.size shouldBe 13
}
test("invalid typecasts of numbers") {
val text = """
%import floats
main {
sub start() {
ubyte @shared bb
bb = 5555 as ubyte
routine(5555 as ubyte)
}
sub routine(ubyte bb) {
bb++
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target, false, text, writeAssembly = true, errors=errors).assertFailure()
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "can't cast"
errors.errors[1] shouldContain "can't cast"
}
test("refuse to round float literal 1") {
val text = """
%option enable_floats
main {
sub start() {
float @shared fl = 3.456 as uword
fl = 1.234 as uword
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target, false, text, errors=errors).assertFailure()
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "can't cast"
errors.errors[1] shouldContain "can't cast"
}
test("refuse to round float literal 2") {
val text = """
%option enable_floats
main {
sub start() {
float @shared fl = 3.456
fl++
fl = fl as uword
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target, false, text, errors=errors).assertFailure()
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "in-place makes no sense"
}
test("refuse to round float literal 3") {
val text = """
%option enable_floats
main {
sub start() {
uword @shared ww = 3.456 as uword
ww++
ww = 3.456 as uword
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target, false, text, errors=errors).assertFailure()
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "can't cast"
errors.errors[1] shouldContain "can't cast"
}
})

View File

@ -431,7 +431,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
else {
val rounded = round(numbervalue)
if(rounded != numbervalue)
throw ExpressionError("refused silent rounding of float to avoid loss of precision", position)
throw ExpressionError("refused rounding of float to avoid loss of precision", position)
rounded
}
}
@ -569,14 +569,18 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
return CastValue(true, NumericLiteralValue(targettype, number, position))
}
DataType.FLOAT -> {
if (targettype == DataType.BYTE && number >= -128 && number <=127)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if (targettype == DataType.UBYTE && number >=0 && number <= 255)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if (targettype == DataType.WORD && number >= -32768 && number <= 32767)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if (targettype == DataType.UWORD && number >=0 && number <= 65535)
return CastValue(true, NumericLiteralValue(targettype, number, position))
try {
if (targettype == DataType.BYTE && number >= -128 && number <= 127)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if (targettype == DataType.UBYTE && number >= 0 && number <= 255)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if (targettype == DataType.WORD && number >= -32768 && number <= 32767)
return CastValue(true, NumericLiteralValue(targettype, number, position))
if (targettype == DataType.UWORD && number >= 0 && number <= 65535)
return CastValue(true, NumericLiteralValue(targettype, number, position))
} catch (x: ExpressionError) {
return CastValue(false, null)
}
}
else -> {}
}