better sql exaple, some inference bugs fixed
This commit is contained in:
parent
f8d2533b48
commit
a2e3f80ab6
93
examples/sqlite_basic.lyng
Normal file
93
examples/sqlite_basic.lyng
Normal 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")
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
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[itSlot] = cls
|
||||
paramTypeMap[slot] = cls
|
||||
}
|
||||
val paramTypeDeclMap = slotTypeDeclByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
|
||||
paramTypeDeclMap[itSlot] = implicitItType
|
||||
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
|
||||
}
|
||||
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,7 +8135,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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user