diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CodeContext.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CodeContext.kt index 3a6c6fc..1a1e7e4 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CodeContext.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CodeContext.kt @@ -31,6 +31,7 @@ sealed class CodeContext { var typeParamDecls: List = emptyList() val pendingInitializations = mutableMapOf() val declaredMembers = mutableSetOf() + val declaredMethodNames = mutableSetOf() val classScopeMembers = mutableSetOf() val memberOverrides = mutableMapOf() val memberFieldIds = mutableMapOf() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 7259d0c..514d860 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -456,7 +456,7 @@ class Compiler( } } - private fun predeclareClassMembers(target: MutableSet, overrides: MutableMap) { + private fun predeclareClassMembers(target: MutableSet, overrides: MutableMap, methodNames: MutableSet? = null) { val saved = cc.savePos() var depth = 0 val modifiers = setOf( @@ -478,18 +478,22 @@ class Compiler( Token.Type.RBRACE -> if (depth == 0) break else depth-- Token.Type.ID -> if (depth == 0) { var sawOverride = false + var sawStatic = false while (t.type == Token.Type.ID && t.value in modifiers) { if (t.value == "override") sawOverride = true + if (t.value == "static") sawStatic = true t = nextNonWs() } when (t.value) { "fun", "fn", "val", "var" -> { + val isMethod = t.value == "fun" || t.value == "fn" val nameToken = nextNonWs() if (nameToken.type == Token.Type.ID) { val afterName = cc.peekNextNonWhitespace() if (afterName.type != Token.Type.DOT) { target.add(nameToken.value) overrides[nameToken.value] = sawOverride + if (isMethod && !sawStatic) methodNames?.add(nameToken.value) } } } @@ -7757,7 +7761,7 @@ class Compiler( classCtx?.let { ctx -> val callableMembers = classScopeCallableMembersByClassName.getOrPut(qualifiedName) { mutableSetOf() } predeclareClassScopeMembers(qualifiedName, ctx.classScopeMembers, callableMembers) - predeclareClassMembers(ctx.declaredMembers, ctx.memberOverrides) + predeclareClassMembers(ctx.declaredMembers, ctx.memberOverrides, ctx.declaredMethodNames) val existingExternInfo = if (isExtern) resolveCompileClassInfo(qualifiedName) else null if (existingExternInfo != null) { ctx.memberFieldIds.putAll(existingExternInfo.fieldIds) @@ -7806,6 +7810,13 @@ class Compiler( ctx.memberFieldIds[param.name] = ctx.nextFieldId++ } } + // Pre-assign method IDs for all declared methods so forward + // references within the class body resolve correctly. + for (method in ctx.declaredMethodNames) { + if (!ctx.memberMethodIds.containsKey(method)) { + ctx.memberMethodIds[method] = ctx.nextMethodId++ + } + } compileClassInfos[qualifiedName] = CompileClassInfo( name = qualifiedName, packageName = packageName, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt index 75a065b..8166f74 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt @@ -926,11 +926,16 @@ open class ObjClass( candidate.methodId } else null } - methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run { + val id = methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run { methodIdMap[name] = nextMethodId nextMethodId++ methodIdMap[name]!! } + // Register the resolved ID so subsequent assignMethodId calls (e.g. for static + // methods) don't reuse the same numeric slot for a different member. + methodIdMap[name] = id + if (id >= nextMethodId) nextMethodId = id + 1 + id } else { methodId } diff --git a/lynglib/src/commonTest/kotlin/OOTest.kt b/lynglib/src/commonTest/kotlin/OOTest.kt index d3db727..9fdcd41 100644 --- a/lynglib/src/commonTest/kotlin/OOTest.kt +++ b/lynglib/src/commonTest/kotlin/OOTest.kt @@ -1199,4 +1199,22 @@ class OOTest { """.trimIndent()) } + @Test + fun testForwardSymbolsUsageMustBeAllowed() = runTest { + eval(""" + class Foo(x) { + fn fn2() { + fn1() + println("fn2") + } + fn fn1() { + println("fn1") + } + } + + val foo = Foo(33) + foo.fn2() + """.trimIndent()) + } + } diff --git a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/ComplexModuleTest.kt b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/ComplexModuleTest.kt index 3e17fb4..bd02a43 100644 --- a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/ComplexModuleTest.kt +++ b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/ComplexModuleTest.kt @@ -111,4 +111,22 @@ class ComplexModuleTest { ) } + @Test + fun testOperatorSlotDispatch() = runTest { + val scope = Script.newScope() + scope.eval( + """ + import lyng.complex + val a = Complex(1.0, 2.0) + val b = Complex(3.0, -1.0) + val product = a * b + assertEquals(5.0, product.re) + assertEquals(5.0, product.im) + val sum = a + Complex(0.0, 0.0) + assertEquals(1.0, sum.re) + assertEquals(2.0, sum.im) + """.trimIndent() + ) + } + }