mirror of
https://github.com/irmen/prog8.git
synced 2026-04-21 17:16:33 +00:00
detect circular aliases, also fix error message for aliased function call with wrong number of args
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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++
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user