From 17694c1d01576701af5a0decbda88db274e5cfcd Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 7 Jan 2022 22:12:13 +0100 Subject: [PATCH] better error handling of invalid number casts --- .../optimizer/ConstantIdentifierReplacer.kt | 2 +- .../compiler/astprocessing/AstChecker.kt | 13 +- compiler/test/TestTypecasts.kt | 114 ++++++++++++++++++ .../prog8/ast/expressions/AstExpressions.kt | 22 ++-- 4 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 compiler/test/TestTypecasts.kt diff --git a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt index 1d026ccbb..07fbe7ff7 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt @@ -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) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 1923e2df6..6a72822ec 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -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) } diff --git a/compiler/test/TestTypecasts.kt b/compiler/test/TestTypecasts.kt new file mode 100644 index 000000000..a43d45aa3 --- /dev/null +++ b/compiler/test/TestTypecasts.kt @@ -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" + } +}) diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index 2a0cf4d82..ab450c16f 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -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 -> {} }