diff --git a/examples/sqlite_basic.lyng b/examples/sqlite_basic.lyng new file mode 100644 index 0000000..b9f7b0c --- /dev/null +++ b/examples/sqlite_basic.lyng @@ -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") diff --git a/lyngio/src/jvmTest/kotlin/net/sergeych/lyng/io/db/sqlite/LyngSqliteModuleTest.kt b/lyngio/src/jvmTest/kotlin/net/sergeych/lyng/io/db/sqlite/LyngSqliteModuleTest.kt index 81130ed..8f249d0 100644 --- a/lyngio/src/jvmTest/kotlin/net/sergeych/lyng/io/db/sqlite/LyngSqliteModuleTest.kt +++ b/lyngio/src/jvmTest/kotlin/net/sergeych/lyng/io/db/sqlite/LyngSqliteModuleTest.kt @@ -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("", 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("", code), scope.importManager).execute(scope) as ObjInt + assertEquals(2L, result.value) + } + @Test fun testNestedTransactionRollbackUsesSavepoint() = runTest { val scope = Script.newScope() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt index de06657..49e7a8e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt @@ -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 } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 7ab2011..ee67076 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -191,6 +191,7 @@ class Compiler( private val lambdaCaptureEntriesByRef: MutableMap> = mutableMapOf() private val classFieldTypesByName: MutableMap> = mutableMapOf() + private val classMemberTypeDeclByName: MutableMap> = mutableMapOf() private val classMethodReturnTypeByName: MutableMap> = mutableMapOf() private val classMethodReturnTypeDeclByName: MutableMap> = mutableMapOf() private val classScopeMembersByClassName: MutableMap> = 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 { + val result = mutableMapOf() + 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, val nextFieldId: Int, val nextMethodId: Int, - val baseNames: List + val baseNames: List, + val memberTypeDecls: Map = emptyMap() ) private val compileClassInfos = mutableMapOf() @@ -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, Boolean> { val args = mutableListOf() @@ -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] ) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt index 804c42b..b34de2c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt @@ -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") diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index fd2719c..40a8755 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -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 ) } }