detect circular aliases, also fix error message for aliased function call with wrong number of args

This commit is contained in:
Irmen de Jong
2025-12-26 14:01:05 +01:00
parent c7052a183e
commit cc0425a2c4
7 changed files with 104 additions and 64 deletions
@@ -455,8 +455,9 @@ jump p8_label_gen_2
}
if(idx>=2) {
val previous = indexedInstructions[idx-2].value
if(previous.opcode==Opcode.LOAD && previous.reg1==reg)
return idx-2 to previous.immediate!!
if(previous.opcode==Opcode.LOAD && previous.reg1==reg && previous.immediate!=null) {
return idx - 2 to previous.immediate!!
}
}
return null
}
@@ -233,56 +233,61 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
}
private fun visitFunctionCall(call: IFunctionCall) {
if(call.target.nameInSource==listOf("rnd") || call.target.nameInSource==listOf("rndw")) {
val target = call.target.targetStatement(program.builtinFunctions)
if(target==null) {
errors.err("rnd() and rndw() builtin functions have been moved into the math module", call.position)
return
}
}
when (val target = call.target.targetStatement(program.builtinFunctions)) {
is Subroutine -> {
val expectedNumberOfArgs: Int = target.parameters.size
if(call.args.size != expectedNumberOfArgs) {
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
invalidNumberOfArgsError(pos, call.args.size, target.parameters.map { it.name })
}
}
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(target.name)
val expectedNumberOfArgs: Int = func.parameters.size
if(call.args.size != expectedNumberOfArgs) {
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
invalidNumberOfArgsError(pos, call.args.size, func.parameters.map {it.name })
}
if(target.name=="memory") {
val name = call.args[0] as? StringLiteral
if(name!=null) {
val processed = name.value.map {
if(it.isLetterOrDigit())
it
else
'_'
}.joinToString("")
val textEncoding = (call as Node).definingModule.textEncoding
call.args[0] = StringLiteral.create(processed, textEncoding, name.position)
call.args[0].linkParents(call as Node)
fun check(target: Statement?, aliasDepth: Int) {
when (target) {
is Subroutine -> {
val expectedNumberOfArgs: Int = target.parameters.size
if(call.args.size != expectedNumberOfArgs) {
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
invalidNumberOfArgsError(pos, call.args.size, target.parameters.map { it.name })
}
}
}
is Label -> {
if(call.args.isNotEmpty()) {
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
errors.err("cannot use arguments when calling a label", pos)
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(target.name)
val expectedNumberOfArgs: Int = func.parameters.size
if(call.args.size != expectedNumberOfArgs) {
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
invalidNumberOfArgsError(pos, call.args.size, func.parameters.map {it.name })
}
if(target.name=="memory") {
val name = call.args[0] as? StringLiteral
if(name!=null) {
val processed = name.value.map {
if(it.isLetterOrDigit())
it
else
'_'
}.joinToString("")
val textEncoding = (call as Node).definingModule.textEncoding
call.args[0] = StringLiteral.create(processed, textEncoding, name.position)
call.args[0].linkParents(call as Node)
}
}
}
is Label -> {
if(call.args.isNotEmpty()) {
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
errors.err("cannot use arguments when calling a label", pos)
}
}
is VarDecl -> {
if(target.type!=VarDeclType.VAR || !target.datatype.isUnsignedWord)
errors.err("wrong address variable datatype, expected uword", call.target.position)
}
is Alias -> {
if(aliasDepth>1000) {
errors.err("circular alias", target.position)
} else {
val actualtarget = target.target.targetStatement(program.builtinFunctions)
check(actualtarget, aliasDepth + 1)
}
}
is StructDecl, is StructFieldRef -> {}
null -> {} // symbol error is given elsewhere
else -> errors.err("cannot call this as a subroutine or function", call.target.position)
}
is VarDecl -> {
if(target.type!=VarDeclType.VAR || !target.datatype.isUnsignedWord)
errors.err("wrong address variable datatype, expected uword", call.target.position)
}
is Alias, is StructDecl, is StructFieldRef -> {}
null -> {}
else -> errors.err("cannot call this as a subroutine or function", call.target.position)
}
check(call.target.targetStatement(program.builtinFunctions), 0)
}
}
@@ -125,8 +125,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
val target = call.target.targetStatement(program.builtinFunctions)
if (target is Subroutine) {
val consideredParamTypes: List<DataType> = target.parameters.map { it.type }
if(argtypes.size != consideredParamTypes.size)
return Pair("invalid number of arguments", call.position)
require(argtypes.size == consideredParamTypes.size)
val mismatch = argtypes.zip(consideredParamTypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
if(mismatch>=0) {
val actual = argtypes[mismatch]
@@ -174,8 +173,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
else if (target is BuiltinFunctionPlaceholder) {
val func = BuiltinFunctions.getValue(target.name)
val consideredParamTypes = func.parameters.map { it.possibleDatatypes }
if(argtypes.size != consideredParamTypes.size)
return Pair("invalid number of arguments", call.position)
require(argtypes.size==consideredParamTypes.size)
argtypes.zip(consideredParamTypes).forEachIndexed { index, pair ->
val anyCompatible = pair.second.any {
if(it.isArray)
+26 -5
View File
@@ -91,7 +91,7 @@ main {
}
}
context("alias") {
context("alias statement") {
test("aliases ok") {
val src="""
main {
@@ -178,12 +178,28 @@ txt {
errors.errors[1] shouldContain "undefined symbol: txt.DEFAULT_WIDTH_XXX"
}
test("function call on alias with wrong param count gives correct error") {
test("aliased function call with wrong args count gives correct error") {
val src="""
main {
sub start() {
alias func = actualfunc
func(1,2)
alias func1 = actualfunc
alias func2 = mkword
alias func3 = func1
alias func4 = func2
; all wrong:
func1(1,2)
func1()
func2(1,2,3,4)
func2()
func3()
func4()
; all ok:
func1(1)
cx16.r0 = func2(1,2)
func3(1)
cx16.r0 = func4(1,2)
sub actualfunc(ubyte a) {
a++
@@ -192,8 +208,13 @@ main {
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false, errors=errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors.size shouldBe 6
errors.errors[0] shouldContain "invalid number of arguments: expected 1 but got 2"
errors.errors[1] shouldContain "invalid number of arguments: expected 1 but got 0"
errors.errors[2] shouldContain "invalid number of arguments: expected 2 but got 4"
errors.errors[3] shouldContain "invalid number of arguments: expected 2 but got 0"
errors.errors[4] shouldContain "invalid number of arguments: expected 1 but got 0"
errors.errors[5] shouldContain "invalid number of arguments: expected 2 but got 0"
}
}
-4
View File
@@ -1,9 +1,6 @@
TODO
====
fix alias function call crash with wrong number of parameters, also make sure it's the correct complete message
Weird Heisenbug
^^^^^^^^^^^^^^^
- BUG: examples/cube3d-float crashes with div by zero error on C64 (works on cx16. ALready broken in v11, v10 still worked)
@@ -47,7 +44,6 @@ Future Things and Ideas
- Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions
- Change scoping rules for qualified symbols so that they don't always start from the root but behave like other programming languages (look in local scope first), maybe only when qualified symbol starts with '.' such as: .local.value = 33
- something to reduce the need to use fully qualified names all the time. 'with' ? Or 'using <prefix>'?
- detect circular aliases and print proper error message for them
- Improve register load order in subroutine call args assignments:
in certain situations (need examples!), the "wrong" order of evaluation of function call arguments is done which results
in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!)
+18 -2
View File
@@ -1,7 +1,23 @@
main {
sub start() {
alias func = actualfunc
func(1,2) ; expected: "invalid number of arguments" got: crash
alias func1 = actualfunc
alias func2 = mkword
alias func3 = func1
alias func4 = func2
; all wrong:
func1(1,2)
func1()
func2(1,2,3,4)
func2()
func3()
func4()
; all ok:
func1(1)
cx16.r0 = func2(1,2)
func3(1)
cx16.r0 = func4(1,2)
sub actualfunc(ubyte a) {
a++
+4 -1
View File
@@ -208,8 +208,11 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
val txt = txt(node)
val library = if(node is PtBlock) node.library else node.definingBlock()?.library==true
if(!library || !skipLibraries) {
if (txt.isNotEmpty())
if (txt.isNotEmpty()) {
if(node is PtSub)
output("")
output(" ".repeat(depth) + txt(node))
}
}
}
}