setting a byte >=128 or word >=32768 now results in an out-of-range error, instead of an invalid casted value

This commit is contained in:
Irmen de Jong 2024-11-22 21:24:04 +01:00
parent 4958463e75
commit f569ce6141
5 changed files with 93 additions and 18 deletions

View File

@ -32,15 +32,9 @@ class VarConstantValueTypeAdjuster(
val declConstValue = decl.value?.constValue(program) val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST) if(declConstValue!=null && (decl.type== VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& declConstValue.type != decl.datatype) { && declConstValue.type != decl.datatype) {
// avoid silent float roundings
if(decl.datatype in IntegerDatatypes && declConstValue.type == DataType.FLOAT) { 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) 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) { } catch (x: UndefinedSymbolError) {

View File

@ -50,15 +50,22 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
} }
} }
VarDeclType.CONST -> { 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)) { if(valueDt.largerThan(decl.datatype)) {
val constValue = decl.value!!.constValue(program)!! val constValue = decl.value!!.constValue(program)!!
errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position) errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position)
} else {
// 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 { } else {
val changed = decl.copy(valueDt) val changed = decl.copy(valueDt)
return listOf(IAstModification.ReplaceNode(decl, changed, parent)) return listOf(IAstModification.ReplaceNode(decl, changed, parent))
} }
} }
}
VarDeclType.MEMORY -> if(!valueType.isWords && !valueType.isBytes) VarDeclType.MEMORY -> if(!valueType.isWords && !valueType.isBytes)
throw FatalAstException("value type for a memory var should be word or byte (address)") throw FatalAstException("value type for a memory var should be word or byte (address)")
} }

View File

@ -3,6 +3,7 @@ package prog8tests.ast
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf import io.kotest.matchers.types.instanceOf
import prog8.ast.expressions.AddressOf import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.BinaryExpression import prog8.ast.expressions.BinaryExpression
@ -16,6 +17,7 @@ import prog8.code.core.DataType
import prog8.code.core.Position import prog8.code.core.Position
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.code.target.Cx16Target import prog8.code.target.Cx16Target
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
class TestConst: FunSpec({ class TestConst: FunSpec({
@ -323,4 +325,82 @@ main {
assignAddr2.identifier.nameInSource shouldBe listOf("buffer") assignAddr2.identifier.nameInSource shouldBe listOf("buffer")
assignAddr2.arrayIndex!!.indexExpr shouldBe instanceOf<BinaryExpression>() assignAddr2.arrayIndex!!.indexExpr shouldBe instanceOf<BinaryExpression>()
} }
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")
}
}) })

View File

@ -629,7 +629,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
when(type) { when(type) {
DataType.UBYTE -> { 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)) return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toByte().toDouble(), position))
if(targettype==DataType.WORD || targettype==DataType.UWORD) if(targettype==DataType.WORD || targettype==DataType.UWORD)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) 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)) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==DataType.UBYTE && number <= 255) if(targettype==DataType.UBYTE && number <= 255)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) 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)) return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toShort().toDouble(), position))
if(targettype==DataType.FLOAT) if(targettype==DataType.FLOAT)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))

View File

@ -1,12 +1,6 @@
TODO 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 is now order dependent due to merges, you must use this order:
%import textio %import textio
%import floats %import floats