better handling of inferred type errors

This commit is contained in:
Irmen de Jong 2020-10-30 16:26:19 +01:00
parent 3ab641aa21
commit 87862f772a
16 changed files with 103 additions and 42 deletions

View File

@ -35,8 +35,9 @@ enum class DataType {
else -> false
}
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
infix fun isNotAssignableTo(targetType: DataType) = !this.isAssignableTo(targetType)
infix fun isNotAssignableTo(targetTypes: Set<DataType>) = !this.isAssignableTo(targetTypes)
infix fun largerThan(other: DataType) =
when {

View File

@ -36,9 +36,12 @@ object InferredTypes {
override fun hashCode(): Int = Objects.hash(isVoid, datatype)
infix fun isAssignableTo(targetDt: InferredType): Boolean {
return isKnown && targetDt.isKnown && (datatype!! isAssignableTo targetDt.datatype!!)
}
infix fun isAssignableTo(targetDt: InferredType): Boolean =
isKnown && targetDt.isKnown && (datatype!! isAssignableTo targetDt.datatype!!)
infix fun isAssignableTo(targetDt: DataType): Boolean =
isKnown && (datatype!! isAssignableTo targetDt)
infix fun isNotAssignableTo(targetDt: InferredType): Boolean = !this.isAssignableTo(targetDt)
infix fun isNotAssignableTo(targetDt: DataType): Boolean = !this.isAssignableTo(targetDt)
}
private val unknownInstance = InferredType.unknown()

View File

@ -348,7 +348,7 @@ internal class AstChecker(private val program: Program,
if(!idt.isKnown) {
errors.err("return type mismatch", assignment.value.position)
}
if(stmt.returntypes.size <= 1 && !(stmt.returntypes.single() isAssignableTo idt.typeOrElse(DataType.BYTE))) {
if(stmt.returntypes.size <= 1 && stmt.returntypes.single() isNotAssignableTo idt.typeOrElse(DataType.BYTE)) {
errors.err("return type mismatch", assignment.value.position)
}
}
@ -593,8 +593,8 @@ internal class AstChecker(private val program: Program,
if(declValue!=null && decl.type==VarDeclType.VAR) {
if(decl.datatype==DataType.STRUCT) {
val valueIdt = declValue.inferType(program)
if(valueIdt.isUnknown)
throw AstException("invalid value type")
if(!valueIdt.isKnown)
throw AstException("unknown dt")
val valueDt = valueIdt.typeOrElse(DataType.STRUCT)
if(valueDt !in ArrayDatatypes)
err("initialisation of struct should be with array value", declValue.position)
@ -769,7 +769,11 @@ internal class AstChecker(private val program: Program,
}
override fun visit(expr: PrefixExpression) {
val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
val idt = expr.inferType(program)
if(!idt.isKnown)
return // any error should be reported elsewhere
val dt = idt.typeOrElse(DataType.STRUCT)
if(expr.operator=="-") {
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
errors.err("can only take negative of a signed number type", expr.position)
@ -1218,7 +1222,7 @@ internal class AstChecker(private val program: Program,
for(elt in value.value.zip(struct.statements)) {
val vardecl = elt.second as VarDecl
val valuetype = elt.first.inferType(program)
if (!valuetype.isKnown || !(valuetype.typeOrElse(DataType.STRUCT) isAssignableTo vardecl.datatype)) {
if (!valuetype.isKnown || valuetype isNotAssignableTo vardecl.datatype) {
errors.err("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position)
return false
}

View File

@ -4,6 +4,7 @@ import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.*
@ -39,7 +40,10 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
}
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
val argITypes = call.args.map { it.inferType(program) }
if(argITypes.any { !it.isKnown })
throw FatalAstException("unknown dt")
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope)
if (target is Subroutine) {
if(call.args.size != target.parameters.size)

View File

@ -187,7 +187,12 @@ open class VarDecl(val type: VarDeclType,
fun createAuto(array: ArrayLiteralValue): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val declaredType = ArrayElementTypes.getValue(array.type.typeOrElse(DataType.STRUCT))
val arrayDt =
if(!array.type.isKnown)
throw FatalAstException("unknown dt")
else
array.type.typeOrElse(DataType.STRUCT)
val declaredType = ArrayElementTypes.getValue(arrayDt)
val arraysize = ArrayIndex.forArray(array)
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
isArray = true, autogeneratedDontRemove = true, position = array.position)

View File

@ -896,8 +896,10 @@ internal class AsmGen(private val program: Program,
}
else -> {
translateExpression(stmt.iterations!!)
val dt = stmt.iterations!!.inferType(program).typeOrElse(DataType.STRUCT)
when (dt) {
val dt = stmt.iterations!!.inferType(program)
if(!dt.isKnown)
throw AssemblyError("unknown dt")
when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> {
out(" inx | lda P8ESTACK_LO,x")
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)

View File

@ -578,7 +578,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if(first is ArrayIndexedExpression && second is ArrayIndexedExpression) {
val arrayVarName1 = asmgen.asmVariableName(first.arrayvar)
val arrayVarName2 = asmgen.asmVariableName(second.arrayvar)
val elementDt = first.inferType(program).typeOrElse(DataType.STRUCT)
val elementIDt = first.inferType(program)
if(!elementIDt.isKnown)
throw AssemblyError("unknown dt")
val elementDt = elementIDt.typeOrElse(DataType.STRUCT)
val firstNum = first.indexer.indexNum
val firstVar = first.indexer.indexVar
@ -612,7 +615,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.translateExpression(first)
asmgen.translateExpression(second)
val datatype = first.inferType(program).typeOrElse(DataType.STRUCT)
val idatatype = first.inferType(program)
if(!idatatype.isKnown)
throw AssemblyError("unknown dt")
val datatype = idatatype.typeOrElse(DataType.STRUCT)
val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, datatype),
targetFromExpr(first, datatype),

View File

@ -53,7 +53,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
val dt = left.inferType(program).typeOrElse(DataType.STRUCT)
val idt = left.inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.typeOrElse(DataType.STRUCT)
when (operator) {
"==" -> {
// if the left operand is an expression, and the right is 0, we can just evaluate that expression.
@ -1512,7 +1515,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateExpression(expr: PrefixExpression) {
translateExpression(expr.expression)
val type = expr.inferType(program).typeOrElse(DataType.STRUCT)
val itype = expr.inferType(program)
if(!itype.isKnown)
throw AssemblyError("unknown dt")
val type = itype.typeOrElse(DataType.STRUCT)
when(expr.operator) {
"+" -> {}
"-" -> {
@ -1547,7 +1553,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
val elementDt = arrayExpr.inferType(program).typeOrElse(DataType.STRUCT)
val elementIDt = arrayExpr.inferType(program)
if(!elementIDt.isKnown)
throw AssemblyError("unknown dt")
val elementDt = elementIDt.typeOrElse(DataType.STRUCT)
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
if(arrayExpr.indexer.indexNum!=null) {
val indexValue = arrayExpr.indexer.constIndex()!! * elementDt.memorySize()

View File

@ -18,7 +18,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
internal fun translate(stmt: ForLoop) {
val iterableDt = stmt.iterable.inferType(program)
if(!iterableDt.isKnown)
throw AssemblyError("can't determine iterable dt")
throw AssemblyError("unknown dt")
when(stmt.iterable) {
is RangeExpr -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()

View File

@ -159,7 +159,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown")
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
@ -175,7 +175,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// pass argument via a register parameter
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown")
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")

View File

@ -55,7 +55,10 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
companion object {
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
val dt = inferType(program, assign).typeOrElse(DataType.STRUCT)
val idt = inferType(program, assign)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.typeOrElse(DataType.STRUCT)
when {
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this)
@ -127,8 +130,10 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
is BuiltinFunctionStatementPlaceholder -> {
val returnType = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
val returnType = value.inferType(program)
if(!returnType.isKnown)
throw AssemblyError("unknown dt")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.STRUCT), expression = value)
}
else -> {
throw AssemblyError("weird call")
@ -136,8 +141,10 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
}
}
else -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt, expression = value)
val dt = value.inferType(program)
if(!dt.isKnown)
throw AssemblyError("unknown dt")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.STRUCT), expression = value)
}
}
}

View File

@ -150,8 +150,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val signature = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(value, signature, false)
val returntype = builtinFunctionReturnType(sub.name, value.args, program)
if(returntype.isUnknown)
throw AssemblyError("weird result type")
if(!returntype.isKnown)
throw AssemblyError("unknown dt")
when(returntype.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A) // function's byte result is in A
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
@ -184,7 +184,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
private fun assignTypeCastedValue(target: AsmAssignTarget, targetDt: DataType, value: Expression, origAssign: AsmAssignment) {
val valueDt = value.inferType(program).typeOrElse(DataType.STRUCT)
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
when(value) {
is IdentifierReference -> {
if(targetDt in WordDatatypes) {

View File

@ -22,7 +22,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when (val value = assign.source.expression!!) {
is PrefixExpression -> {
// A = -A , A = +A, A = ~A, A = not A
val type = value.inferType(program).typeOrElse(DataType.STRUCT)
val itype = value.inferType(program)
if(!itype.isKnown)
throw AssemblyError("unknown dt")
val type = itype.typeOrElse(DataType.STRUCT)
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(assign.target, type)
@ -210,7 +213,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun tryRemoveRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean {
if (target.datatype == value.type) {
val childDt = value.expression.inferType(program).typeOrElse(DataType.STRUCT)
val childIDt = value.expression.inferType(program)
if(!childIDt.isKnown)
throw AssemblyError("unknown dt")
val childDt = childIDt.typeOrElse(DataType.STRUCT)
if (value.type.equalsSize(childDt) || value.type.largerThan(childDt)) {
// this typecast is redundant here; the rest of the code knows how to deal with the uncasted value.
inplaceModification(target, operator, value.expression)
@ -1096,7 +1102,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (4): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
asmgen.translateExpression(value)
val valueDt = value.inferType(program).typeOrElse(DataType.STRUCT)
val valueiDt = value.inferType(program)
if(!valueiDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueiDt.typeOrElse(DataType.STRUCT)
fun multiplyWord() {
asmgen.out("""

View File

@ -477,7 +477,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
when (expr.operator) {
"%" -> {
if (cv == 1.0) {
return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position)
val idt = expr.inferType(program)
if(!idt.isKnown)
throw FatalAstException("unknown dt")
return NumericLiteralValue(idt.typeOrElse(DataType.STRUCT), 0, expr.position)
} else if (cv == 2.0) {
expr.operator = "&"
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
@ -606,8 +609,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (amount == 0) {
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when (targetDt) {
val targetIDt = expr.left.inferType(program)
if(!targetIDt.isKnown)
throw FatalAstException("unknown dt")
when (val targetDt = targetIDt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) {
return NumericLiteralValue(targetDt, 0, expr.position)
@ -639,7 +644,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (amount == 0) {
return expr.left
}
when (expr.left.inferType(program).typeOrElse(DataType.STRUCT)) {
val idt = expr.left.inferType(program)
if(!idt.isKnown)
throw FatalAstException("unknown dt")
when (idt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
if (amount >= 8) {
return NumericLiteralValue.optimalInteger(0, expr.position)

View File

@ -2,16 +2,15 @@
TODO
====
- get rid of all the .typeOrElse(STRUCT) 'shortcuts' and replace them with proper error handling
- make memset(w) and memcopy able to work with >256 bytes
- make memset and memcopy use the ROM routines on the CX16
- after that: make memset and memcopy use the ROM routines on the CX16
- calling convention for builtin functions no longer via stack but via statically allocated vars inside the subroutine proc (just as normal subroutines)
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_'
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
- use VIC banking to move up the graphics bitmap memory location. Don't move it under the ROM though as that would require IRQ disabling and memory bank swapping for every bitmap manipulation
- add some primitives/subroutines/examples for using custom char sets, copying the default charset.
- some better handling of recursive subroutines? via %option recursive?: allocate all params and local vars on estack, don't allow nested subroutines, can begin by first not allowing any local variables just fixing the parameters
- add a c-64 example for using custom char sets, copying (part of) the default charset.
- some support for recursive subroutines? via %option recursive?: allocate all params and local vars on estack, don't allow nested subroutines, can begin by first not allowing any local variables just fixing the parameters
- get rid of all other TODO's in the code ;-)
More optimizations
@ -21,8 +20,7 @@ Add more compiler optimizations to the existing ones.
- further optimize assignment codegeneration, such as the following:
- binexpr splitting (beware self-referencing expressions and asm code ballooning though)
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise. Especially for built-in functions!
- can such parameter passing to subroutines be optimized to avoid copying?
- detect var->var argument passing to subroutines and avoid the second variable and copying of the value
- more optimizations on the language AST level
- more optimizations on the final assembly source level
- note: subroutine inlining is abandoned because of problems referencing non-local stuff. Can't move everything around.

View File

@ -191,6 +191,7 @@ main {
sub facing_away(ubyte edgePointsIdx) -> ubyte {
; simplistic visibility determination by checking the Z component of the surface normal
; TODO: actually take the line of sight vector into account
ubyte p1 = shipdata.facesPoints[edgePointsIdx]
edgePointsIdx++
ubyte p2 = shipdata.facesPoints[edgePointsIdx]