better sql exaple, some inference bugs fixed

This commit is contained in:
Sergey Chernov 2026-04-16 00:54:00 +03:00
parent f8d2533b48
commit a2e3f80ab6
6 changed files with 382 additions and 47 deletions

View File

@ -0,0 +1,93 @@
import lyng.io.db
import lyng.io.db.sqlite
import lyng.time
println("SQLite demo: typed open, generic open, result sets, generated keys, nested rollback")
// The typed helper is the simplest entry point when you know you want SQLite.
val db = openSqlite(":memory:")
db.transaction { tx ->
// Keep schema creation and data changes inside one transaction block.
tx.execute("create table task(id integer primary key autoincrement, title text not null, done integer not null, due_date date not null)")
// execute(...) is for side-effect statements. Generated keys are read from
// ExecutionResult rather than from a synthetic row-returning INSERT.
val firstInsert = tx.execute(
"insert into task(title, done, due_date) values(?, ?, ?)",
"Write a SQLite example",
false,
Date(2026, 4, 15)
)
val firstGeneratedKeys = firstInsert.getGeneratedKeys()
val firstId = firstGeneratedKeys.toList()[0][0]
assertEquals(1, firstId)
tx.execute(
"insert into task(title, done, due_date) values(?, ?, ?)",
"Review the DB API",
true,
Date(2026, 4, 16)
)
// Nested transactions are real savepoints. If the inner block fails,
// only the nested work is rolled back.
try {
tx.transaction { inner ->
inner.execute(
"insert into task(title, done, due_date) values(?, ?, ?)",
"This row is rolled back",
false,
Date(2026, 4, 17)
)
throw IllegalStateException("demonstrate nested rollback")
}
} catch (_: IllegalStateException) {
println("Nested transaction rolled back as expected")
}
// select(...) is for row-producing statements. ResultSet exposes metadata,
// cheap emptiness checks, iteration, and conversion to a plain list.
val tasks = tx.select("select id, title, done, due_date from task order by id")
assertEquals(false, tasks.isEmpty())
assertEquals(2, tasks.size())
println("Columns:")
for (column in tasks.columns) {
println(" " + column.name + " -> " + column.sqlType + " (native " + column.nativeType + ")")
}
val taskRows = tasks.toList()
println("Rows:")
for (row in taskRows) {
// Name lookups are case-insensitive and values are already converted.
println(" #" + row["ID"] + " " + row["title"] + " done=" + row["done"] + " due=" + row["due_date"])
}
// If values need to survive after the transaction closes, copy them now.
val snapshot = tx.select("select title, due_date from task order by id").toList()
assertEquals("Write a SQLite example", snapshot[0]["title"])
assertEquals(Date(2026, 4, 16), snapshot[1]["due_date"])
val count = tx.select("select count(*) as count from task").toList()[0]["count"]
assertEquals(2, count)
println("Visible rows after nested rollback: $count")
}
// The generic entry point stays useful for config-driven code.
val genericDb = openDatabase(
"sqlite::memory:",
Map(
"foreignKeys" => true,
"busyTimeoutMillis" => 1000
)
)
val answer = genericDb.transaction { tx ->
tx.select("select 42 as answer").toList()[0]["answer"]
}
assertEquals(42, answer)
println("Generic openDatabase(...) also works: answer=$answer")
println("OK")

View File

@ -18,32 +18,24 @@
package net.sergeych.lyng.io.db.sqlite
import kotlinx.coroutines.test.runTest
import kotlinx.datetime.TimeZone
import net.sergeych.lyng.Compiler
import net.sergeych.lyng.ExecutionError
import net.sergeych.lyng.ModuleScope
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Script
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjBuffer
import net.sergeych.lyng.obj.ObjBool
import net.sergeych.lyng.obj.ObjDateTime
import net.sergeych.lyng.obj.ObjExternCallable
import net.sergeych.lyng.obj.ObjInt
import net.sergeych.lyng.obj.ObjInstant
import net.sergeych.lyng.obj.ObjMap
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjString
import net.sergeych.lyng.obj.raiseAsExecutionError
import net.sergeych.lyng.Source
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.obj.requiredArg
import net.sergeych.lyng.requireScope
import kotlinx.datetime.TimeZone
import java.nio.file.Files
import kotlin.io.path.deleteIfExists
import kotlin.time.Instant
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
import kotlin.time.Instant
class LyngSqliteModuleTest {
@ -100,6 +92,51 @@ class LyngSqliteModuleTest {
assertEquals(2L, count.value)
}
@Test
fun testImportedDatabaseOpenersPreserveDeclaredReturnTypesForInference() = runTest {
val scope = Script.newScope()
createSqliteModule(scope.importManager)
val code = """
import lyng.io.db
import lyng.io.db.sqlite
val typedDb = openSqlite(":memory:")
typedDb.transaction { 1 }
val genericDb = openDatabase("sqlite::memory:", Map())
genericDb.transaction { 2 }
""".trimIndent()
Compiler.compile(Source("<sqlite-inference>", code), scope.importManager).execute(scope)
}
@Test
fun testTransactionLambdaParameterTypeIsInferredFromDatabaseSignature() = runTest {
val scope = Script.newScope()
createSqliteModule(scope.importManager)
val code = """
import lyng.io.db
import lyng.io.db.sqlite
import lyng.time
val db = openSqlite(":memory:")
db.transaction { tx ->
tx.execute("create table item(id integer primary key autoincrement, name text not null, due_date date not null)")
tx.execute("insert into item(name, due_date) values(?, ?)", "outer", Date(2026, 4, 16))
tx.transaction { inner ->
inner.execute("insert into item(name, due_date) values(?, ?)", "inner", Date(2026, 4, 17))
1
}
2
}
""".trimIndent()
val result = Compiler.compile(Source("<sqlite-lambda-inference>", code), scope.importManager).execute(scope) as ObjInt
assertEquals(2L, result.value)
}
@Test
fun testNestedTransactionRollbackUsesSavepoint() = runTest {
val scope = Script.newScope()

View File

@ -85,6 +85,15 @@ internal suspend fun executeClassDecl(
}
if (spec.isExtern) {
val parentClasses = spec.baseSpecs.mapNotNull { baseSpec ->
val rec = scope[baseSpec.name]
val cls = rec?.value as? ObjClass
when {
cls != null -> cls
baseSpec.name == "Exception" -> ObjException.Root
else -> null
}
}
val rec = scope[spec.className]
val existing = rec?.value as? ObjClass
val resolved = if (existing != null) {
@ -94,8 +103,42 @@ internal suspend fun executeClassDecl(
} else {
null
}
val stub = resolved ?: ObjInstanceClass(spec.className).apply { this.isAbstract = true }
spec.declaredName?.let { scope.addItem(it, false, stub) }
val stub = resolved ?: ObjInstanceClass(spec.className, *parentClasses.toTypedArray()).apply {
this.isAbstract = true
constructorMeta = spec.constructorArgs
spec.constructorArgs?.params?.forEach { p ->
if (p.accessType != null) {
createField(
p.name,
ObjNull,
isMutable = p.accessType == AccessType.Var,
visibility = p.visibility ?: Visibility.Public,
declaringClass = this,
pos = Pos.builtIn,
isTransient = p.isTransient,
type = ObjRecord.Type.ConstructorField,
fieldId = spec.constructorFieldIds?.get(p.name)
)
}
}
}
spec.declaredName?.let { name ->
if (scope.getLocalRecordDirect(name)?.value !== stub) {
scope.addItem(name, false, stub)
}
}
if (resolved == null && (spec.bodyInit != null || spec.initScope.isNotEmpty())) {
val classScope = stub.classScope ?: scope.createChildScope(newThisObj = stub).also {
it.currentClassCtx = stub
stub.classScope = it
}
spec.bodyInit?.let { executeBytecodeWithSeed(classScope, it, "extern class body init") }
if (spec.initScope.isNotEmpty()) {
for (s in spec.initScope) {
executeBytecodeWithSeed(classScope, s, "extern class init")
}
}
}
return stub
}

View File

@ -191,6 +191,7 @@ class Compiler(
private val lambdaCaptureEntriesByRef: MutableMap<ValueFnRef, List<net.sergeych.lyng.bytecode.LambdaCaptureEntry>> =
mutableMapOf()
private val classFieldTypesByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf()
private val classMemberTypeDeclByName: MutableMap<String, MutableMap<String, TypeDecl>> = mutableMapOf()
private val classMethodReturnTypeByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf()
private val classMethodReturnTypeDeclByName: MutableMap<String, MutableMap<String, TypeDecl>> = mutableMapOf()
private val classScopeMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
@ -652,10 +653,31 @@ class Compiler(
val cls = clsFromScope ?: clsFromImports ?: resolveClassByName(name) ?: return null
val fieldIds = cls.instanceFieldIdMap()
val methodIds = cls.instanceMethodIdMap(includeAbstract = true)
val memberTypeDecls = collectClassMemberTypeDecls(cls)
val baseNames = cls.directParents.map { it.className }
val nextFieldId = (fieldIds.values.maxOrNull() ?: -1) + 1
val nextMethodId = (methodIds.values.maxOrNull() ?: -1) + 1
return CompileClassInfo(name, cls.logicalPackageName, fieldIds, methodIds, nextFieldId, nextMethodId, baseNames)
return CompileClassInfo(
name,
cls.logicalPackageName,
fieldIds,
methodIds,
nextFieldId,
nextMethodId,
baseNames,
memberTypeDecls = memberTypeDecls
)
}
private fun collectClassMemberTypeDecls(cls: ObjClass): Map<String, TypeDecl> {
val result = mutableMapOf<String, TypeDecl>()
for (name in cls.instanceFieldIdMap().keys) {
cls.getInstanceMemberOrNull(name, includeAbstract = true)?.typeDecl?.let { result[name] = it }
}
for (name in cls.instanceMethodIdMap(includeAbstract = true).keys) {
cls.getInstanceMemberOrNull(name, includeAbstract = true)?.typeDecl?.let { result[name] = it }
}
return result
}
private data class BaseMemberIds(
@ -1672,7 +1694,8 @@ class Compiler(
val methodIds: Map<String, Int>,
val nextFieldId: Int,
val nextMethodId: Int,
val baseNames: List<String>
val baseNames: List<String>,
val memberTypeDecls: Map<String, TypeDecl> = emptyMap()
)
private val compileClassInfos = mutableMapOf<String, CompileClassInfo>()
@ -2934,7 +2957,15 @@ class Compiler(
inferReceiverTypeFromRef(left)
} else null
val itType = implicitItTypeForMemberLambda(left, next.value)
val lambda = parseLambdaExpression(receiverType, implicitItType = itType)
val expectedCallableType = expectedCallableArgumentType(
FieldRef(left, next.value, isOptional),
0
)
val lambda = parseLambdaExpression(
receiverType,
implicitItType = itType,
expectedCallableType = expectedCallableType
)
val argPos = next.pos
val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos))
operand = when (left) {
@ -3341,7 +3372,8 @@ class Compiler(
private suspend fun parseLambdaExpression(
expectedReceiverType: String? = null,
wrapAsExtensionCallable: Boolean = false,
implicitItType: TypeDecl? = null
implicitItType: TypeDecl? = null,
expectedCallableType: TypeDecl.Function? = null
): ObjRef {
// lambda args are different:
val startPos = cc.currentPos()
@ -3357,16 +3389,36 @@ class Compiler(
val hasImplicitIt = argsDeclaration == null
val slotParamNames = if (hasImplicitIt) paramNames + "it" else paramNames
val paramSlotPlan = buildParamSlotPlan(slotParamNames)
if (implicitItType != null) {
val cls = resolveTypeDeclObjClass(implicitItType)
val itSlot = paramSlotPlan.slots["it"]?.index
if (itSlot != null) {
if (cls != null) {
val paramTypeMap = slotTypeByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
paramTypeMap[itSlot] = cls
fun seedLambdaParamType(name: String, typeDecl: TypeDecl?) {
if (typeDecl == null) return
val slot = paramSlotPlan.slots[name]?.index ?: return
resolveTypeDeclObjClass(typeDecl)?.let { cls ->
val paramTypeMap = slotTypeByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
paramTypeMap[slot] = cls
}
val paramTypeDeclMap = slotTypeDeclByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
paramTypeDeclMap[slot] = typeDecl
}
if (argsDeclaration != null) {
val expectedParams = expectedCallableType?.params.orEmpty()
argsDeclaration.params.forEachIndexed { index, param ->
val effectiveType = if ((param.type == TypeDecl.TypeAny || param.type == TypeDecl.TypeNullableAny) &&
index < expectedParams.size
) {
expectedParams[index]
} else {
param.type
}
val paramTypeDeclMap = slotTypeDeclByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
paramTypeDeclMap[itSlot] = implicitItType
if (effectiveType != TypeDecl.TypeAny && effectiveType != TypeDecl.TypeNullableAny) {
seedLambdaParamType(param.name, effectiveType)
}
}
} else {
val effectiveImplicitItType = implicitItType
?: expectedCallableType?.params?.singleOrNull()
if (effectiveImplicitItType != null) {
seedLambdaParamType("it", effectiveImplicitItType)
}
}
@ -4807,6 +4859,23 @@ class Compiler(
return null
}
private fun classMemberTypeDecl(targetClass: ObjClass?, name: String): TypeDecl? {
if (targetClass == null) return null
if (targetClass == ObjDynamic.type) return TypeDecl.TypeAny
val member = targetClass.getInstanceMemberOrNull(name, includeAbstract = true)
val declaringName = member?.declaringClass?.className
val declaringClass = member?.declaringClass
declaringClass?.classScope?.getLocalRecordDirect(name)?.typeDecl?.let { return it }
targetClass.classScope?.getLocalRecordDirect(name)?.typeDecl?.let { return it }
if (declaringName != null) {
classMemberTypeDeclByName[declaringName]?.get(name)?.let { return it }
resolveCompileClassInfo(declaringName)?.memberTypeDecls?.get(name)?.let { return it }
}
classMemberTypeDeclByName[targetClass.className]?.get(name)?.let { return it }
resolveCompileClassInfo(targetClass.className)?.memberTypeDecls?.get(name)?.let { return it }
return member?.typeDecl
}
private fun classMethodReturnClass(targetClass: ObjClass?, name: String): ObjClass? {
if (targetClass == null) return null
if (targetClass == ObjDynamic.type) return ObjDynamic.type
@ -4896,7 +4965,7 @@ class Compiler(
is ImplicitThisMemberRef -> {
val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName()
val targetClass = typeName?.let { resolveClassByName(it) } ?: return null
targetClass.getInstanceMemberOrNull(ref.name, includeAbstract = true)?.typeDecl?.let { return it }
classMemberTypeDecl(targetClass, ref.name)?.let { return it }
classFieldTypesByName[targetClass.className]?.get(ref.name)
?.let { return TypeDecl.Simple(it.className, false) }
classMethodReturnTypeDeclByName[targetClass.className]?.get(ref.name)?.let { return it }
@ -4905,7 +4974,7 @@ class Compiler(
is FieldRef -> {
val targetDecl = resolveReceiverTypeDecl(ref.target) ?: return null
val targetClass = resolveTypeDeclObjClass(targetDecl) ?: resolveReceiverClassForMember(ref.target)
targetClass?.getInstanceMemberOrNull(ref.name, includeAbstract = true)?.typeDecl?.let { return it }
classMemberTypeDecl(targetClass, ref.name)?.let { return it }
classFieldTypesByName[targetClass?.className]?.get(ref.name)
?.let { return TypeDecl.Simple(it.className, false) }
when (targetDecl) {
@ -5848,7 +5917,7 @@ class Compiler(
return
}
val seededCallable = lookupNamedCallableRecord(target)
if (seededCallable != null && seededCallable.type == ObjRecord.Type.Fun && seededCallable.value !is ObjExternCallable) {
if (seededCallable != null && seededCallable.type == ObjRecord.Type.Fun) {
return
}
val decl = (resolveReceiverTypeDecl(target) as? TypeDecl.Function)
@ -6426,7 +6495,8 @@ class Compiler(
*/
private suspend fun parseArgs(
expectedTailBlockReceiver: String? = null,
implicitItType: TypeDecl? = null
implicitItType: TypeDecl? = null,
expectedTailBlockTarget: ObjRef? = null
): Pair<List<ParsedArgument>, Boolean> {
val args = mutableListOf<ParsedArgument>()
@ -6484,7 +6554,11 @@ class Compiler(
var lastBlockArgument = false
if (end.type == Token.Type.LBRACE) {
// last argument - callable
val callableAccessor = parseLambdaExpression(expectedTailBlockReceiver, implicitItType = implicitItType)
val callableAccessor = parseLambdaExpression(
expectedTailBlockReceiver,
implicitItType = implicitItType,
expectedCallableType = expectedTailBlockTarget?.let { expectedCallableArgumentType(it, args.size) }
)
args += ParsedArgument(
ExpressionStatement(callableAccessor, end.pos),
end.pos
@ -6560,6 +6634,7 @@ class Compiler(
): ObjRef {
var detectedBlockArgument = blockArgument
val expectedReceiver = tailBlockReceiverType(left)
val expectedTailCallable = expectedCallableArgumentType(left, 0)
val withReceiver = when (left) {
is LocalVarRef -> if (left.name == "with") left.name else null
is LocalSlotRef -> if (left.name == "with") left.name else null
@ -6571,7 +6646,10 @@ class Compiler(
// allow any subsequent selectors (like ".last()") to be absorbed
// into the lambda body. This ensures expected order:
// foo { ... }.bar() == (foo { ... }).bar()
val callableAccessor = parseLambdaExpression(expectedReceiver)
val callableAccessor = parseLambdaExpression(
expectedReceiver,
expectedCallableType = expectedTailCallable
)
listOf(ParsedArgument(ExpressionStatement(callableAccessor, cc.currentPos()), cc.currentPos()))
} else {
if (withReceiver != null) {
@ -6580,7 +6658,11 @@ class Compiler(
val end = cc.next()
if (end.type == Token.Type.LBRACE) {
val receiverType = inferReceiverTypeFromArgs(parsedArgs)
val callableAccessor = parseLambdaExpression(receiverType, wrapAsExtensionCallable = true)
val callableAccessor = parseLambdaExpression(
receiverType,
wrapAsExtensionCallable = true,
expectedCallableType = expectedCallableArgumentType(left, parsedArgs.size)
)
parsedArgs += ParsedArgument(ExpressionStatement(callableAccessor, end.pos), end.pos)
detectedBlockArgument = true
} else {
@ -6588,7 +6670,7 @@ class Compiler(
}
parsedArgs
} else {
val r = parseArgs(expectedReceiver)
val r = parseArgs(expectedReceiver, expectedTailBlockTarget = left)
detectedBlockArgument = r.second
r.first
}
@ -6710,6 +6792,26 @@ class Compiler(
return cls?.className
}
private fun expectedCallableArgumentType(target: ObjRef, argIndex: Int): TypeDecl.Function? {
val decl = (resolveReceiverTypeDecl(target) ?: seedTypeDeclFromRef(target)) as? TypeDecl.Function
?: return null
val params = when (target) {
is FieldRef,
is ImplicitThisMemberRef,
is ThisFieldSlotRef,
is ClassScopeMemberRef -> decl.params
else -> buildList {
decl.receiver?.let { add(it) }
addAll(decl.params)
}
}
if (argIndex < params.size) {
return params[argIndex] as? TypeDecl.Function
}
val ellipsis = params.lastOrNull() as? TypeDecl.Ellipsis ?: return null
return ellipsis.elementType as? TypeDecl.Function
}
private fun inferReceiverTypeFromRef(ref: ObjRef): String? {
return when (ref) {
is LocalSlotRef -> {
@ -7928,7 +8030,8 @@ class Compiler(
methodIds = ctx.memberMethodIds.toMap(),
nextFieldId = ctx.nextFieldId,
nextMethodId = ctx.nextMethodId,
baseNames = baseSpecs.map { it.name }
baseNames = baseSpecs.map { it.name },
memberTypeDecls = classMemberTypeDeclByName[qualifiedName]?.toMap() ?: emptyMap()
)
}
}
@ -7944,7 +8047,8 @@ class Compiler(
methodIds = ctx.memberMethodIds.toMap(),
nextFieldId = ctx.nextFieldId,
nextMethodId = ctx.nextMethodId,
baseNames = baseSpecs.map { it.name }
baseNames = baseSpecs.map { it.name },
memberTypeDecls = classMemberTypeDeclByName[qualifiedName]?.toMap() ?: emptyMap()
)
}
}
@ -8031,10 +8135,11 @@ class Compiler(
methodIds = ctx.memberMethodIds.toMap(),
nextFieldId = ctx.nextFieldId,
nextMethodId = ctx.nextMethodId,
baseNames = baseSpecs.map { it.name }
baseNames = baseSpecs.map { it.name },
memberTypeDecls = classMemberTypeDeclByName[qualifiedName]?.toMap() ?: emptyMap()
)
}
}
}
registerClassScopeFieldType(outerClassName, declaredName, qualifiedName)
// restore if no body starts here
cc.restorePos(saved)
@ -9036,6 +9141,15 @@ class Compiler(
pendingTypeParamStack.removeLast()
}
if (parentContext is CodeContext.ClassBody && !isStatic && extTypeName == null) {
classMemberTypeDeclByName.getOrPut(parentContext.name) { mutableMapOf() }[name] = TypeDecl.Function(
receiver = receiverTypeDecl,
params = argsDeclaration.params.map { it.type },
returnType = returnTypeDecl ?: TypeDecl.TypeAny,
nullable = false
)
}
var isDelegated = false
var delegateExpression: Statement? = null
if (cc.peekNextNonWhitespace().type == Token.Type.BY) {
@ -9195,9 +9309,19 @@ class Compiler(
val rawFnStatements = parsedFnStatements?.let { unwrapBytecodeDeep(it) }
val inferredReturnClass = returnTypeDecl?.let { resolveTypeDeclObjClass(it) }
?: inferReturnClassFromStatement(rawFnStatements)
if (declKind == SymbolKind.MEMBER && extTypeName == null) {
val ownerClassName = (parentContext as? CodeContext.ClassBody)?.name
if (ownerClassName != null) {
if (parentContext is CodeContext.ClassBody && !isStatic && extTypeName == null) {
val ownerClassName = parentContext.name
run {
val memberTypeDecl = TypeDecl.Function(
receiver = receiverTypeDecl,
params = argsDeclaration.params.map { it.type },
returnType = returnTypeDecl
?: inferredReturnClass?.let { TypeDecl.Simple(it.className, false) }
?: TypeDecl.TypeAny,
nullable = false
)
classMemberTypeDeclByName
.getOrPut(ownerClassName) { mutableMapOf() }[name] = memberTypeDecl
val returnDecl = returnTypeDecl
?: inferredReturnClass?.let { TypeDecl.Simple(it.className, false) }
if (returnDecl != null) {
@ -9771,7 +9895,8 @@ class Compiler(
pos = Pos.builtIn,
declaringClass = stub,
type = ObjRecord.Type.Field,
fieldId = fieldId
fieldId = fieldId,
typeDecl = info.memberTypeDecls[fieldName]
)
}
}
@ -9786,7 +9911,8 @@ class Compiler(
declaringClass = stub,
isAbstract = true,
type = ObjRecord.Type.Fun,
methodId = methodId
methodId = methodId,
typeDecl = info.memberTypeDecls[methodName]
)
}
}

View File

@ -73,6 +73,35 @@ internal suspend fun executeFunctionDecl(
return value
}
}
if (spec.actualExtern && spec.extTypeName == null && spec.parentIsClassBody) {
val cls = scope.thisObj as? ObjClass
if (cls != null) {
val existing = cls.members[spec.name]
if (existing != null) {
cls.members[spec.name] = existing.copy(
typeDecl = existing.typeDecl ?: spec.typeDecl
)
val memberValue = cls.members[spec.name]?.value ?: existing.value
val local = scope.getLocalRecordDirect(spec.name)
if (local != null) {
scope.objects[spec.name] = local.copy(
value = memberValue,
typeDecl = local.typeDecl ?: spec.typeDecl
)
} else {
scope.addItem(
spec.name,
false,
memberValue,
spec.visibility,
callSignature = spec.externCallSignature,
typeDecl = spec.typeDecl
)
}
return memberValue
}
}
}
if (spec.isDelegated) {
val delegateExpr = spec.delegateExpression ?: scope.raiseError("delegated function missing delegate")

View File

@ -803,12 +803,19 @@ open class Scope(
fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend ScopeFacade.() -> Obj) {
val newFn = net.sergeych.lyng.obj.ObjExternCallable.fromBridge { fn() }
for (name in names) {
val existing = objects[name]
addItem(
name,
false,
existing?.isMutable ?: false,
newFn,
visibility = existing?.visibility ?: Visibility.Public,
writeVisibility = existing?.writeVisibility,
recordType = ObjRecord.Type.Fun,
callSignature = callSignature
isAbstract = false,
isClosed = existing?.isClosed ?: false,
isOverride = existing?.isOverride ?: false,
callSignature = callSignature ?: existing?.callSignature,
typeDecl = existing?.typeDecl
)
}
}