no longer report unknown type errors as well for unknown symbols,

added a bunch more unit tests for symbol scoping rules
This commit is contained in:
Irmen de Jong 2021-11-23 22:36:28 +01:00
parent 0c2f30fd45
commit b00db4f8a2
3 changed files with 228 additions and 8 deletions

View File

@ -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) {

View File

@ -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<ParentSentinel>()
}
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<PostIncrDecr>()
}
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"
}
})

View File

@ -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) {