Fix apply/inc-dec handling and re-enable more ScriptTests

This commit is contained in:
Sergey Chernov 2026-01-30 18:19:55 +03:00
parent eaa5713eaf
commit 64fa305aa7
6 changed files with 134 additions and 52 deletions

View File

@ -67,24 +67,40 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
for (i in params.indices) { for (i in params.indices) {
val a = params[i] val a = params[i]
val value = arguments.list[i] val value = arguments.list[i]
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, val recordType = if (declaringClass != null && a.accessType != null) {
ObjRecord.Type.ConstructorField
} else {
ObjRecord.Type.Argument
}
scope.addItem(
a.name,
(a.accessType ?: defaultAccessType).isMutable,
value.byValueCopy(), value.byValueCopy(),
a.visibility ?: defaultVisibility, a.visibility ?: defaultVisibility,
recordType = ObjRecord.Type.Argument, recordType = recordType,
declaringClass = declaringClass, declaringClass = declaringClass,
isTransient = a.isTransient) isTransient = a.isTransient
)
} }
return return
} }
} }
fun assign(a: Item, value: Obj) { fun assign(a: Item, value: Obj) {
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, val recordType = if (declaringClass != null && a.accessType != null) {
ObjRecord.Type.ConstructorField
} else {
ObjRecord.Type.Argument
}
scope.addItem(
a.name,
(a.accessType ?: defaultAccessType).isMutable,
value.byValueCopy(), value.byValueCopy(),
a.visibility ?: defaultVisibility, a.visibility ?: defaultVisibility,
recordType = ObjRecord.Type.Argument, recordType = recordType,
declaringClass = declaringClass, declaringClass = declaringClass,
isTransient = a.isTransient) isTransient = a.isTransient
)
} }
// Prepare positional args and parameter count, handle tail-block binding // Prepare positional args and parameter count, handle tail-block binding
@ -243,4 +259,4 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
val visibility: Visibility? = null, val visibility: Visibility? = null,
val isTransient: Boolean = false, val isTransient: Boolean = false,
) )
} }

View File

@ -391,7 +391,7 @@ class Compiler(
return LocalVarRef(name, pos) return LocalVarRef(name, pos)
} }
resolutionSink?.reference(name, pos) resolutionSink?.reference(name, pos)
if (allowUnresolvedRefs) { if (allowUnresolvedRefs || (name.isNotEmpty() && name[0].isUpperCase())) {
return LocalVarRef(name, pos) return LocalVarRef(name, pos)
} }
throw ScriptError(pos, "unresolved name: $name") throw ScriptError(pos, "unresolved name: $name")
@ -1741,6 +1741,11 @@ class Compiler(
else -> null else -> null
} }
val effectiveAccess = if (isClassDeclaration && access == null) {
AccessType.Var
} else {
access
}
// type information (semantic + mini syntax) // type information (semantic + mini syntax)
val (typeInfo, miniType) = parseTypeDeclarationWithMini() val (typeInfo, miniType) = parseTypeDeclarationWithMini()
@ -1757,7 +1762,7 @@ class Compiler(
t.pos, t.pos,
isEllipsis, isEllipsis,
defaultValue, defaultValue,
access, effectiveAccess,
visibility, visibility,
isTransient isTransient
) )

View File

@ -190,6 +190,7 @@ class BytecodeCompiler(
private fun compileRef(ref: ObjRef): CompiledValue? { private fun compileRef(ref: ObjRef): CompiledValue? {
return when (ref) { return when (ref) {
is ConstRef -> compileConst(ref.constValue) is ConstRef -> compileConst(ref.constValue)
is IncDecRef -> compileIncDec(ref, true)
is LocalSlotRef -> { is LocalSlotRef -> {
if (ref.name == "__PACKAGE__") { if (ref.name == "__PACKAGE__") {
return compileNameLookup(ref.name) return compileNameLookup(ref.name)
@ -1625,6 +1626,83 @@ class BytecodeCompiler(
} }
} }
val thisFieldTarget = ref.target as? ThisFieldSlotRef
if (thisFieldTarget != null) {
val nameId = builder.addConst(BytecodeConst.StringVal(thisFieldTarget.name))
if (nameId > 0xFFFF) return null
val current = allocSlot()
builder.emit(Opcode.GET_THIS_MEMBER, nameId, current)
updateSlotType(current, SlotType.OBJ)
val oneSlot = allocSlot()
val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
updateSlotType(oneSlot, SlotType.OBJ)
val result = allocSlot()
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
if (wantResult && ref.isPost) {
val old = allocSlot()
builder.emit(Opcode.MOVE_OBJ, current, old)
builder.emit(op, current, oneSlot, result)
builder.emit(Opcode.SET_THIS_MEMBER, nameId, result)
return CompiledValue(old, SlotType.OBJ)
}
builder.emit(op, current, oneSlot, result)
builder.emit(Opcode.SET_THIS_MEMBER, nameId, result)
return CompiledValue(result, SlotType.OBJ)
}
val implicitTarget = ref.target as? ImplicitThisMemberRef
if (implicitTarget != null) {
val nameId = builder.addConst(BytecodeConst.StringVal(implicitTarget.name))
if (nameId > 0xFFFF) return null
val current = allocSlot()
builder.emit(Opcode.GET_THIS_MEMBER, nameId, current)
updateSlotType(current, SlotType.OBJ)
val oneSlot = allocSlot()
val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
updateSlotType(oneSlot, SlotType.OBJ)
val result = allocSlot()
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
if (wantResult && ref.isPost) {
val old = allocSlot()
builder.emit(Opcode.MOVE_OBJ, current, old)
builder.emit(op, current, oneSlot, result)
builder.emit(Opcode.SET_THIS_MEMBER, nameId, result)
return CompiledValue(old, SlotType.OBJ)
}
builder.emit(op, current, oneSlot, result)
builder.emit(Opcode.SET_THIS_MEMBER, nameId, result)
return CompiledValue(result, SlotType.OBJ)
}
val fieldTarget = ref.target as? FieldRef
if (fieldTarget != null) {
if (fieldTarget.isOptional) return null
val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null
val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
if (nameId > 0xFFFF) return null
val current = allocSlot()
builder.emit(Opcode.GET_FIELD, receiver.slot, nameId, current)
updateSlotType(current, SlotType.OBJ)
val oneSlot = allocSlot()
val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
updateSlotType(oneSlot, SlotType.OBJ)
val result = allocSlot()
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
if (wantResult && ref.isPost) {
val old = allocSlot()
builder.emit(Opcode.MOVE_OBJ, current, old)
builder.emit(op, current, oneSlot, result)
builder.emit(Opcode.SET_FIELD, receiver.slot, nameId, result)
return CompiledValue(old, SlotType.OBJ)
}
builder.emit(op, current, oneSlot, result)
builder.emit(Opcode.SET_FIELD, receiver.slot, nameId, result)
return CompiledValue(result, SlotType.OBJ)
}
val indexTarget = ref.target as? IndexRef ?: return null val indexTarget = ref.target as? IndexRef ?: return null
if (indexTarget.optionalRef) return null if (indexTarget.optionalRef) return null
val receiver = compileRefWithFallback(indexTarget.targetRef, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(indexTarget.targetRef, null, Pos.builtIn) ?: return null

View File

@ -725,7 +725,8 @@ open class Obj {
(thisObj as? ObjInstance)?.let { (thisObj as? ObjInstance)?.let {
body.callOn(ApplyScope(this, it.instanceScope)) body.callOn(ApplyScope(this, it.instanceScope))
} ?: run { } ?: run {
body.callOn(this) val appliedScope = createChildScope(newThisObj = thisObj)
body.callOn(ApplyScope(this, appliedScope))
} }
thisObj thisObj
} }

View File

@ -118,28 +118,34 @@ open class ObjClass(
/** /**
* Map of public member names to their effective storage keys in instanceScope.objects. * Map of public member names to their effective storage keys in instanceScope.objects.
* This is pre-calculated to avoid MRO traversal and string concatenation during common access. * Cached and invalidated by layoutVersion to reflect newly added members.
*/ */
val publicMemberResolution: Map<String, String> by lazy { private var publicMemberResolutionVersion: Int = -1
val res = mutableMapOf<String, String>() private var publicMemberResolutionCache: Map<String, String> = emptyMap()
// Traverse MRO in REVERSED order so that child classes override parent classes in the map. val publicMemberResolution: Map<String, String>
for (cls in mro.reversed()) { get() {
if (cls.className == "Obj") continue if (publicMemberResolutionVersion == layoutVersion) return publicMemberResolutionCache
for ((name, rec) in cls.members) { val res = mutableMapOf<String, String>()
if (rec.visibility == Visibility.Public) { // Traverse MRO in REVERSED order so that child classes override parent classes in the map.
val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name for (cls in mro.reversed()) {
res[name] = key if (cls.className == "Obj") continue
} for ((name, rec) in cls.members) {
} if (rec.visibility == Visibility.Public) {
cls.classScope?.objects?.forEach { (name, rec) -> val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
if (rec.visibility == Visibility.Public && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) { res[name] = key
val key = if (rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name }
res[name] = key }
cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.visibility == Visibility.Public && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) {
val key = if (rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
res[name] = key
}
} }
} }
publicMemberResolutionCache = res
publicMemberResolutionVersion = layoutVersion
return res
} }
res
}
val classNameObj by lazy { ObjString(className) } val classNameObj by lazy { ObjString(className) }

View File

@ -2671,7 +2671,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testLet() = runTest { fun testLet() = runTest {
eval( eval(
@ -2685,7 +2684,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testApply() = runTest { fun testApply() = runTest {
eval( eval(
@ -2700,7 +2698,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testApplyThis() = runTest { fun testApplyThis() = runTest {
eval( eval(
@ -2717,7 +2714,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testApplyFromStatic() = runTest { fun testApplyFromStatic() = runTest {
eval( eval(
@ -2754,7 +2750,6 @@ class ScriptTest {
} }
} }
@Ignore("incremental enable")
@Test @Test
fun TestApplyFromKotlin() = runTest { fun TestApplyFromKotlin() = runTest {
val scope = Script.newScope() val scope = Script.newScope()
@ -2770,7 +2765,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testParallels() = runTest { fun testParallels() = runTest {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
@ -2797,7 +2791,6 @@ class ScriptTest {
} }
} }
@Ignore("incremental enable")
@Test @Test
fun testParallels2() = runTest { fun testParallels2() = runTest {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
@ -2845,7 +2838,6 @@ class ScriptTest {
} }
} }
@Ignore("incremental enable")
@Test @Test
fun testExtend() = runTest() { fun testExtend() = runTest() {
eval( eval(
@ -2879,7 +2871,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testToFlow() = runTest() { fun testToFlow() = runTest() {
val c = Scope() val c = Scope()
@ -2888,7 +2879,6 @@ class ScriptTest {
assertEquals(listOf(1, 2, 3), arr.toFlow(c).map { it.toInt() }.toList()) assertEquals(listOf(1, 2, 3), arr.toFlow(c).map { it.toInt() }.toList())
} }
@Ignore("incremental enable")
@Test @Test
fun testAssociateBy() = runTest() { fun testAssociateBy() = runTest() {
eval( eval(
@ -2916,7 +2906,6 @@ class ScriptTest {
// assertEquals("foo1", pm.modules["lyng.foo"]!!.deferredModule.await().eval("foo()").toString()) // assertEquals("foo1", pm.modules["lyng.foo"]!!.deferredModule.await().eval("foo()").toString())
// } // }
@Ignore("incremental enable")
@Test @Test
fun testImports2() = runTest() { fun testImports2() = runTest() {
val foosrc = """ val foosrc = """
@ -2936,7 +2925,6 @@ class ScriptTest {
assertEquals("foo1", scope.eval(src).toString()) assertEquals("foo1", scope.eval(src).toString())
} }
@Ignore("incremental enable")
@Test @Test
fun testImports3() = runTest { fun testImports3() = runTest {
val foosrc = """ val foosrc = """
@ -2968,7 +2956,6 @@ class ScriptTest {
assertEquals("foo1 / bar1", scope.eval(src).toString()) assertEquals("foo1 / bar1", scope.eval(src).toString())
} }
@Ignore("incremental enable")
@Test @Test
fun testImportsCircular() = runTest { fun testImportsCircular() = runTest {
val foosrc = """ val foosrc = """
@ -3002,7 +2989,6 @@ class ScriptTest {
assertEquals("foo1 / bar1", scope.eval(src).toString()) assertEquals("foo1 / bar1", scope.eval(src).toString())
} }
@Ignore("incremental enable")
@Test @Test
fun testDefaultImportManager() = runTest { fun testDefaultImportManager() = runTest {
val scope = Scope.new() val scope = Scope.new()
@ -3029,7 +3015,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testMaps() = runTest { fun testMaps() = runTest {
eval( eval(
@ -3063,7 +3048,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testExternDeclarations() = runTest { fun testExternDeclarations() = runTest {
eval( eval(
@ -3087,7 +3071,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testExternExtension() = runTest { fun testExternExtension() = runTest {
eval( eval(
@ -3098,7 +3081,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testBuffer() = runTest { fun testBuffer() = runTest {
eval( eval(
@ -3125,7 +3107,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testBufferEncodings() = runTest { fun testBufferEncodings() = runTest {
eval( eval(
@ -3148,7 +3129,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testBufferCompare() = runTest { fun testBufferCompare() = runTest {
eval( eval(
@ -3174,7 +3154,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testInstant() = runTest { fun testInstant() = runTest {
eval( eval(
@ -3214,7 +3193,6 @@ class ScriptTest {
delay(1000) delay(1000)
} }
@Ignore("incremental enable")
@Test @Test
fun testTimeStatics() = runTest { fun testTimeStatics() = runTest {
eval( eval(
@ -3236,7 +3214,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testInstantFormatting() = runTest { fun testInstantFormatting() = runTest {
eval( eval(
@ -3251,7 +3228,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testDateTimeComprehensive() = runTest { fun testDateTimeComprehensive() = runTest {
eval( eval(