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
|
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()
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user