Fix method slot ID collision between instance and static methods
When createField() was called with a pre-assigned methodId, the ID was used but methodIdMap was not updated and nextMethodId was not advanced. This caused assignMethodId() for static methods to reuse slot IDs already occupied by instance methods. In complex.lyng, fromInt/imaginary got IDs 12/14 (same as plus/mul), causing binary operator dispatch via CALL_MEMBER_SLOT to call the wrong function (e.g. a*b would invoke imaginary instead of mul). Fix: after computing effectiveMethodId in createField, always register it in methodIdMap and advance nextMethodId past it so subsequent auto-assignments start from a clean range. Also pre-assigns method IDs for non-static fun/fn declarations during class body pre-scan so forward references resolve correctly in class bodies, and adds ComplexModuleTest coverage for operator slot dispatch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2f145a0ea7
commit
f145a90845
@ -31,6 +31,7 @@ sealed class CodeContext {
|
|||||||
var typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
|
var typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
|
||||||
val pendingInitializations = mutableMapOf<String, Pos>()
|
val pendingInitializations = mutableMapOf<String, Pos>()
|
||||||
val declaredMembers = mutableSetOf<String>()
|
val declaredMembers = mutableSetOf<String>()
|
||||||
|
val declaredMethodNames = mutableSetOf<String>()
|
||||||
val classScopeMembers = mutableSetOf<String>()
|
val classScopeMembers = mutableSetOf<String>()
|
||||||
val memberOverrides = mutableMapOf<String, Boolean>()
|
val memberOverrides = mutableMapOf<String, Boolean>()
|
||||||
val memberFieldIds = mutableMapOf<String, Int>()
|
val memberFieldIds = mutableMapOf<String, Int>()
|
||||||
|
|||||||
@ -456,7 +456,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun predeclareClassMembers(target: MutableSet<String>, overrides: MutableMap<String, Boolean>) {
|
private fun predeclareClassMembers(target: MutableSet<String>, overrides: MutableMap<String, Boolean>, methodNames: MutableSet<String>? = null) {
|
||||||
val saved = cc.savePos()
|
val saved = cc.savePos()
|
||||||
var depth = 0
|
var depth = 0
|
||||||
val modifiers = setOf(
|
val modifiers = setOf(
|
||||||
@ -478,18 +478,22 @@ class Compiler(
|
|||||||
Token.Type.RBRACE -> if (depth == 0) break else depth--
|
Token.Type.RBRACE -> if (depth == 0) break else depth--
|
||||||
Token.Type.ID -> if (depth == 0) {
|
Token.Type.ID -> if (depth == 0) {
|
||||||
var sawOverride = false
|
var sawOverride = false
|
||||||
|
var sawStatic = false
|
||||||
while (t.type == Token.Type.ID && t.value in modifiers) {
|
while (t.type == Token.Type.ID && t.value in modifiers) {
|
||||||
if (t.value == "override") sawOverride = true
|
if (t.value == "override") sawOverride = true
|
||||||
|
if (t.value == "static") sawStatic = true
|
||||||
t = nextNonWs()
|
t = nextNonWs()
|
||||||
}
|
}
|
||||||
when (t.value) {
|
when (t.value) {
|
||||||
"fun", "fn", "val", "var" -> {
|
"fun", "fn", "val", "var" -> {
|
||||||
|
val isMethod = t.value == "fun" || t.value == "fn"
|
||||||
val nameToken = nextNonWs()
|
val nameToken = nextNonWs()
|
||||||
if (nameToken.type == Token.Type.ID) {
|
if (nameToken.type == Token.Type.ID) {
|
||||||
val afterName = cc.peekNextNonWhitespace()
|
val afterName = cc.peekNextNonWhitespace()
|
||||||
if (afterName.type != Token.Type.DOT) {
|
if (afterName.type != Token.Type.DOT) {
|
||||||
target.add(nameToken.value)
|
target.add(nameToken.value)
|
||||||
overrides[nameToken.value] = sawOverride
|
overrides[nameToken.value] = sawOverride
|
||||||
|
if (isMethod && !sawStatic) methodNames?.add(nameToken.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7757,7 +7761,7 @@ class Compiler(
|
|||||||
classCtx?.let { ctx ->
|
classCtx?.let { ctx ->
|
||||||
val callableMembers = classScopeCallableMembersByClassName.getOrPut(qualifiedName) { mutableSetOf() }
|
val callableMembers = classScopeCallableMembersByClassName.getOrPut(qualifiedName) { mutableSetOf() }
|
||||||
predeclareClassScopeMembers(qualifiedName, ctx.classScopeMembers, callableMembers)
|
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
|
val existingExternInfo = if (isExtern) resolveCompileClassInfo(qualifiedName) else null
|
||||||
if (existingExternInfo != null) {
|
if (existingExternInfo != null) {
|
||||||
ctx.memberFieldIds.putAll(existingExternInfo.fieldIds)
|
ctx.memberFieldIds.putAll(existingExternInfo.fieldIds)
|
||||||
@ -7806,6 +7810,13 @@ class Compiler(
|
|||||||
ctx.memberFieldIds[param.name] = ctx.nextFieldId++
|
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(
|
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||||
name = qualifiedName,
|
name = qualifiedName,
|
||||||
packageName = packageName,
|
packageName = packageName,
|
||||||
|
|||||||
@ -926,11 +926,16 @@ open class ObjClass(
|
|||||||
candidate.methodId
|
candidate.methodId
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run {
|
val id = methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run {
|
||||||
methodIdMap[name] = nextMethodId
|
methodIdMap[name] = nextMethodId
|
||||||
nextMethodId++
|
nextMethodId++
|
||||||
methodIdMap[name]!!
|
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 {
|
} else {
|
||||||
methodId
|
methodId
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1199,4 +1199,22 @@ class OOTest {
|
|||||||
""".trimIndent())
|
""".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())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user