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 package net.sergeych.lyng.io.db.sqlite
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.datetime.TimeZone
import net.sergeych.lyng.Compiler
import net.sergeych.lyng.ExecutionError import net.sergeych.lyng.ExecutionError
import net.sergeych.lyng.ModuleScope import net.sergeych.lyng.ModuleScope
import net.sergeych.lyng.Pos import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Script import net.sergeych.lyng.Script
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.Source
import net.sergeych.lyng.obj.ObjBuffer import net.sergeych.lyng.obj.*
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.obj.requiredArg import net.sergeych.lyng.obj.requiredArg
import net.sergeych.lyng.requireScope import net.sergeych.lyng.requireScope
import kotlinx.datetime.TimeZone
import java.nio.file.Files import java.nio.file.Files
import kotlin.io.path.deleteIfExists import kotlin.io.path.deleteIfExists
import kotlin.time.Instant
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertTrue import kotlin.test.assertTrue
import kotlin.time.Instant
class LyngSqliteModuleTest { class LyngSqliteModuleTest {
@ -100,6 +92,51 @@ class LyngSqliteModuleTest {
assertEquals(2L, count.value) 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 @Test
fun testNestedTransactionRollbackUsesSavepoint() = runTest { fun testNestedTransactionRollbackUsesSavepoint() = runTest {
val scope = Script.newScope() val scope = Script.newScope()

View File

@ -85,6 +85,15 @@ internal suspend fun executeClassDecl(
} }
if (spec.isExtern) { 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 rec = scope[spec.className]
val existing = rec?.value as? ObjClass val existing = rec?.value as? ObjClass
val resolved = if (existing != null) { val resolved = if (existing != null) {
@ -94,8 +103,42 @@ internal suspend fun executeClassDecl(
} else { } else {
null null
} }
val stub = resolved ?: ObjInstanceClass(spec.className).apply { this.isAbstract = true } val stub = resolved ?: ObjInstanceClass(spec.className, *parentClasses.toTypedArray()).apply {
spec.declaredName?.let { scope.addItem(it, false, stub) } 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 return stub
} }

View File

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

View File

@ -73,6 +73,35 @@ internal suspend fun executeFunctionDecl(
return value 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) { if (spec.isDelegated) {
val delegateExpr = spec.delegateExpression ?: scope.raiseError("delegated function missing delegate") 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) { fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend ScopeFacade.() -> Obj) {
val newFn = net.sergeych.lyng.obj.ObjExternCallable.fromBridge { fn() } val newFn = net.sergeych.lyng.obj.ObjExternCallable.fromBridge { fn() }
for (name in names) { for (name in names) {
val existing = objects[name]
addItem( addItem(
name, name,
false, existing?.isMutable ?: false,
newFn, newFn,
visibility = existing?.visibility ?: Visibility.Public,
writeVisibility = existing?.writeVisibility,
recordType = ObjRecord.Type.Fun, recordType = ObjRecord.Type.Fun,
callSignature = callSignature isAbstract = false,
isClosed = existing?.isClosed ?: false,
isOverride = existing?.isOverride ?: false,
callSignature = callSignature ?: existing?.callSignature,
typeDecl = existing?.typeDecl
) )
} }
} }