diff --git a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt index 7ab3be0d8..87a8a32a2 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt @@ -32,15 +32,9 @@ class VarConstantValueTypeAdjuster( val declConstValue = decl.value?.constValue(program) if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST) && declConstValue.type != decl.datatype) { - // avoid silent float roundings if(decl.datatype in IntegerDatatypes && declConstValue.type == DataType.FLOAT) { + // avoid silent float roundings errors.err("refused truncating of float to avoid loss of precision", decl.value!!.position) - } else if(decl.datatype!=DataType.BOOL) { - // cast the numeric literal to the appropriate datatype of the variable if it's not boolean - declConstValue.linkParents(decl) - val cast = declConstValue.cast(decl.datatype, true) - if (cast.isValid) - return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl)) } } } catch (x: UndefinedSymbolError) { diff --git a/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt b/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt index 66c1083af..e01527bef 100644 --- a/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt +++ b/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt @@ -50,13 +50,20 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter, } } VarDeclType.CONST -> { - // change the vardecl type itself as well, but only if it's smaller + // change the vardecl type itself as well, but only if new type is smaller if(valueDt.largerThan(decl.datatype)) { val constValue = decl.value!!.constValue(program)!! errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position) } else { - val changed = decl.copy(valueDt) - return listOf(IAstModification.ReplaceNode(decl, changed, parent)) + // don't make it signed if it was unsigned and vice versa + if(valueDt in SignedDatatypes && decl.datatype !in SignedDatatypes || + valueDt !in SignedDatatypes && decl.datatype in SignedDatatypes) { + val constValue = decl.value!!.constValue(program)!! + errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position) + } else { + val changed = decl.copy(valueDt) + return listOf(IAstModification.ReplaceNode(decl, changed, parent)) + } } } VarDeclType.MEMORY -> if(!valueType.isWords && !valueType.isBytes) diff --git a/compiler/test/ast/TestConst.kt b/compiler/test/ast/TestConst.kt index c82efcce1..68966cc65 100644 --- a/compiler/test/ast/TestConst.kt +++ b/compiler/test/ast/TestConst.kt @@ -3,6 +3,7 @@ package prog8tests.ast import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.instanceOf import prog8.ast.expressions.AddressOf import prog8.ast.expressions.BinaryExpression @@ -16,6 +17,7 @@ import prog8.code.core.DataType import prog8.code.core.Position import prog8.code.target.C64Target import prog8.code.target.Cx16Target +import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.compileText class TestConst: FunSpec({ @@ -323,4 +325,82 @@ main { assignAddr2.identifier.nameInSource shouldBe listOf("buffer") assignAddr2.arrayIndex!!.indexExpr shouldBe instanceOf() } + + test("out of range const byte and word give correct error") { + var src=""" +main { + sub start() { + const byte MIN_BYTE = -129 + const word MIN_WORD = -32769 + const byte MAX_BYTE = 128 + const word MAX_WORD = 32768 + } +}""" + + val errors = ErrorReporterForTests() + compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldBe null + errors.errors.size shouldBe 4 + errors.errors[0] shouldContain "out of range" + errors.errors[1] shouldContain "out of range" + errors.errors[2] shouldContain "out of range" + errors.errors[3] shouldContain "out of range" + } + + test("out of range var byte and word give correct error") { + var src=""" +main { + sub start() { + byte @shared v_MIN_BYTE = -129 + word @shared v_MIN_WORD = -32769 + byte @shared v_MAX_BYTE = 128 + word @shared v_MAX_WORD = 32768 + } +}""" + + val errors = ErrorReporterForTests() + compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldBe null + errors.errors.size shouldBe 8 + errors.errors[0] shouldContain "out of range" + errors.errors[2] shouldContain "out of range" + errors.errors[4] shouldContain "out of range" + errors.errors[6] shouldContain "out of range" + } + + test("out of range const byte and word no errors with explicit cast if possible") { + var src=""" +main { + sub start() { + const byte MIN_BYTE = -129 as byte ; still error + const word MIN_WORD = -32769 as word ; still error + const byte MAX_BYTE = 128 as byte + const word MAX_WORD = 32768 as word + } +}""" + + val errors = ErrorReporterForTests() + compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldBe null + errors.errors.size shouldBe 4 + errors.errors[0] shouldContain(":4:31: const declaration needs a compile-time constant") + errors.errors[1] shouldContain(":4:32: no cast available") + errors.errors[2] shouldContain(":5:31: const declaration needs a compile-time constant") + errors.errors[3] shouldContain(":5:32: no cast available") + } + + test("out of range var byte and word no errors with explicit cast if possible") { + var src=""" +main { + sub start() { + byte @shared v_min_byte2 = -129 as byte ; still error + word @shared v_min_word2 = -32769 as word ; still error + byte @shared v_min_byte3 = 255 as byte + word @shared v_min_word3 = 50000 as word + } +}""" + + val errors = ErrorReporterForTests() + compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldBe null + errors.errors.size shouldBe 2 + errors.errors[0] shouldContain(":4:37: no cast available") + errors.errors[1] shouldContain(":5:37: no cast available") + } }) diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index f35b03973..1882a7b89 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -629,7 +629,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed when(type) { DataType.UBYTE -> { - if(targettype==DataType.BYTE) + if(targettype==DataType.BYTE && (number in 0.0..127.0 || !implicit)) return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toByte().toDouble(), position)) if(targettype==DataType.WORD || targettype==DataType.UWORD) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) @@ -667,7 +667,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) if(targettype==DataType.UBYTE && number <= 255) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) - if(targettype==DataType.WORD) + if(targettype==DataType.WORD && (number <= 32767 || !implicit)) return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toShort().toDouble(), position)) if(targettype==DataType.FLOAT) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 7ed338ef3..b0ee2da40 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,12 +1,6 @@ TODO ==== -this should not be allowed: -const byte MAX_BYTE = 128 ; actual value ends up as -128 -const word MAX_WORD = 32768 ; actual value ends up as -32768 -(how are vars behaving? what if you explicitly cast - that should work?) - - import is now order dependent due to merges, you must use this order: %import textio %import floats