diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 5cc40b4fb..82ab03e41 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -428,8 +428,15 @@ internal class AstChecker(private val program: Program, if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) { if(targetDt.isIterable) errors.err("cannot assign value to string or array", assignment.value.position) - else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) - errors.err("type of value $valueDt doesn't match target $targetDt", assignment.value.position) + else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) { + if(targetDt.isUnknown) { + if(assignment.target.identifier?.targetStatement(program)!=null) + errors.err("target datatype is unknown", assignment.target.position) + // otherwise, another error about missing symbol is already reported. + } else { + errors.err("type of value $valueDt doesn't match target $targetDt", assignment.value.position) + } + } } if(assignment.value is TypecastExpression) { diff --git a/compiler/test/TestScoping.kt b/compiler/test/TestScoping.kt index 8d75ed836..cb4d77ce5 100644 --- a/compiler/test/TestScoping.kt +++ b/compiler/test/TestScoping.kt @@ -3,6 +3,7 @@ package prog8tests import io.kotest.assertions.withClue import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.instanceOf import io.kotest.matchers.types.shouldBeSameInstanceAs import prog8.ast.GlobalNamespace @@ -10,13 +11,15 @@ import prog8.ast.base.ParentSentinel import prog8.ast.expressions.NumericLiteralValue import prog8.ast.statements.* import prog8.compiler.target.C64Target +import prog8tests.helpers.ErrorReporterForTests +import prog8tests.helpers.assertFailure import prog8tests.helpers.assertSuccess import prog8tests.helpers.compileText class TestScoping: FunSpec({ - test("testModulesParentIsGlobalNamespace") { + test("modules parent is global namespace") { val src = """ main { sub start() { @@ -31,7 +34,7 @@ class TestScoping: FunSpec({ module.parent.parent shouldBe instanceOf() } - test("testAnonScopeVarsMovedIntoSubroutineScope") { + test("anon scope vars moved into subroutine scope") { val src = """ main { sub start() { @@ -69,7 +72,7 @@ class TestScoping: FunSpec({ repeatbody.statements[1] shouldBe instanceOf() } - test("testLabelsWithAnonScopes") { + test("labels with anon scopes") { val src = """ main { sub start() { @@ -133,4 +136,214 @@ class TestScoping: FunSpec({ } } + test("good subroutine call without qualified names") { + val text=""" + main { + sub start() { + routine() + routine2() + + sub routine2() { + } + } + sub routine() { + start() + } + } + """ + compileText(C64Target, false, text, writeAssembly = false).assertSuccess() + } + + test("wrong subroutine call without qualified names") { + val text=""" + main { + sub start() { + sub routine2() { + } + } + sub routine() { + routine2() + } + } + """ + val errors= ErrorReporterForTests() + compileText(C64Target, false, text, writeAssembly = false, errors = errors).assertFailure() + errors.errors.size shouldBe 1 + errors.errors[0] shouldContain "undefined symbol: routine2" + } + + test("good subroutine calls with qualified names (from root)") { + val text=""" + main { + sub start() { + main.routine() + main.start.routine2() + + sub routine2() { + } + } + sub routine() { + main.start.routine2() + } + } + """ + compileText(C64Target, false, text, writeAssembly = false).assertSuccess() + } + + test("wrong subroutine calls with qualified names (not from root)") { + val text=""" + main { + sub start() { + start.routine2() + wrong.start.routine2() + sub routine2() { + } + } + sub routine() { + start.routine2() + wrong.start.routine2() + } + } + """ + val errors= ErrorReporterForTests() + compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure() + errors.errors.size shouldBe 4 + errors.errors[0] shouldContain "undefined symbol: start.routine2" + errors.errors[1] shouldContain "undefined symbol: wrong.start.routine2" + errors.errors[2] shouldContain "undefined symbol: start.routine2" + errors.errors[3] shouldContain "undefined symbol: wrong.start.routine2" + } + + test("good variables without qualified names") { + val text=""" + main { + ubyte v1 + + sub start() { + ubyte v2 + v1=1 + v2=2 + + sub routine2() { + ubyte v3 + v1=1 + v2=2 + v3=3 + } + } + sub routine() { + ubyte v4 + v1=1 + v4=4 + } + } + """ + compileText(C64Target, false, text, writeAssembly = false).assertSuccess() + } + + test("wrong variables without qualified names") { + val text=""" + main { + ubyte v1 + + sub start() { + ubyte v2 + v1=1 + v2=2 + v3=3 ; can't access + v4=4 ; can't access + sub routine2() { + ubyte v3 + v1=1 + v2=2 + v3=3 + v4=3 ;can't access + } + } + sub routine() { + ubyte v4 + v1=1 + v2=2 ; can't access + v3=3 ; can't access + v4=4 + } + } + """ + val errors= ErrorReporterForTests() + compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure() + errors.errors.size shouldBe 5 + errors.errors[0] shouldContain "undefined symbol: v3" + errors.errors[1] shouldContain "undefined symbol: v4" + errors.errors[2] shouldContain "undefined symbol: v4" + errors.errors[3] shouldContain "undefined symbol: v2" + errors.errors[4] shouldContain "undefined symbol: v3" + } + + test("good variable refs with qualified names (from root)") { + val text=""" + main { + sub start() { + uword xx + xx = &main.routine + main.routine(5) + main.routine.value = 5 + main.routine.arg = 5 + xx = &main.routine.nested + main.routine.nested(5) + main.routine.nested.nestedvalue = 5 + main.routine.nested.arg2 = 5 + } + + sub routine(ubyte arg) { + ubyte value + + sub nested(ubyte arg2) { + ubyte nestedvalue + } + } + } + """ + compileText(C64Target, false, text, writeAssembly = false).assertSuccess() + } + + test("wrong variable refs with qualified names (not from root)") { + val text=""" + main { + sub start() { + uword xx + xx = &routine + routine(5) + routine.value = 5 + routine.arg = 5 + xx = &routine.nested + routine.nested(5) + routine.nested.nestedvalue = 5 + routine.nested.arg2 = 5 + xx = &wrong.routine + wrong.routine.value = 5 + } + + sub routine(ubyte arg) { + ubyte value + + sub nested(ubyte arg2) { + ubyte nestedvalue + } + } + } + """ + val errors= ErrorReporterForTests() + compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure() + errors.errors.size shouldBe 10 + errors.errors[0] shouldContain "undefined symbol: routine" + errors.errors[1] shouldContain "undefined symbol: routine" + errors.errors[2] shouldContain "undefined symbol: routine.value" + errors.errors[3] shouldContain "undefined symbol: routine.arg" + errors.errors[4] shouldContain "undefined symbol: routine.nested" + errors.errors[5] shouldContain "undefined symbol: routine.nested" + errors.errors[6] shouldContain "undefined symbol: routine.nested.nestedvalue" + errors.errors[7] shouldContain "undefined symbol: routine.nested.arg2" + errors.errors[8] shouldContain "undefined symbol: wrong.routine" + errors.errors[9] shouldContain "undefined symbol: wrong.routine.value" + } }) diff --git a/compiler/test/TestSubroutines.kt b/compiler/test/TestSubroutines.kt index 7bf4de318..d5481b608 100644 --- a/compiler/test/TestSubroutines.kt +++ b/compiler/test/TestSubroutines.kt @@ -127,7 +127,7 @@ class TestSubroutines: FunSpec({ (call.args.single() as IdentifierReference).nameInSource.single() shouldBe "thing" } - test("arrayParameterNotYetAllowed_ButShouldPerhapsBe") { + test("array param not yet allowd (but should perhaps be?)") { // note: the *parser* accepts this as it is valid *syntax*, // however, it's not (yet) valid for the compiler val text = """ @@ -187,7 +187,7 @@ class TestSubroutines: FunSpec({ func.statements.isEmpty() shouldBe true } - test("testUwordParameterAndNormalVarIndexedAsArrayWorkAsDirectMemoryRead") { + test("uword param and normal varindexed as array work as DirectMemoryRead") { val text=""" main { sub thing(uword rr) { @@ -232,7 +232,7 @@ class TestSubroutines: FunSpec({ (valueZZexpr.right as NumericLiteralValue).number.toInt() shouldBe 3 } - test("testUwordParameterAndNormalVarIndexedAsArrayWorkAsMemoryWrite") { + test("uword param and normal varindexed as array work as MemoryWrite") { val text=""" main { sub thing(uword rr) {