Update compile-time resolution and tests

This commit is contained in:
Sergey Chernov 2026-02-03 02:07:29 +03:00
parent 8f60a84e3b
commit 523b9d338b
46 changed files with 3771 additions and 967 deletions

View File

@ -6,3 +6,9 @@
- If you need a wrapper for delegated properties, check for `getValue` explicitly and return a concrete `Statement` object when missing; avoid `onNotFoundResult` lambdas. - If you need a wrapper for delegated properties, check for `getValue` explicitly and return a concrete `Statement` object when missing; avoid `onNotFoundResult` lambdas.
- If wasmJs browser tests hang, first run `:lynglib:wasmJsNodeTest` and look for wasm compilation errors; hangs usually mean module instantiation failed. - If wasmJs browser tests hang, first run `:lynglib:wasmJsNodeTest` and look for wasm compilation errors; hangs usually mean module instantiation failed.
- Do not increase test timeouts to mask wasm generation errors; fix the invalid IR instead. - Do not increase test timeouts to mask wasm generation errors; fix the invalid IR instead.
## Type inference notes (notes/type_system_spec.md)
- Nullability is Kotlin-style: `T` non-null, `T?` nullable, `!!` asserts non-null.
- `void` is a singleton of class `Void` (syntax sugar for return type).
- Object member access requires explicit cast; remove `inspect` from Object and use `toInspectString()` instead.
- Do not reintroduce bytecode fallback opcodes (e.g., `GET_NAME`, `EVAL_*`, `CALL_FALLBACK`) or runtime name-resolution fallbacks; all symbol resolution must stay compile-time only.

View File

@ -1,5 +1,7 @@
# Scopes and Closures: resolution and safety # Scopes and Closures: resolution and safety
Attention to AI: name lookup is ibsolete and must not be used with bytecode compiler
This page documents how name resolution works with `ClosureScope`, how to avoid recursion pitfalls, and how to safely capture and execute callbacks that need access to outer locals. This page documents how name resolution works with `ClosureScope`, how to avoid recursion pitfalls, and how to safely capture and execute callbacks that need access to outer locals.
## Why this matters ## Why this matters

View File

@ -0,0 +1,24 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sergeych.lyng
/**
* Compile-time call metadata for known functions. Used to select lambda receiver semantics.
*/
data class CallSignature(
val tailBlockReceiverType: String? = null
)

View File

@ -26,12 +26,25 @@ import net.sergeych.lyng.obj.ObjRecord
* Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols * Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols
* from [closureScope] with proper precedence * from [closureScope] with proper precedence
*/ */
class ClosureScope(val callScope: Scope, val closureScope: Scope) : class ClosureScope(
val callScope: Scope,
val closureScope: Scope,
private val preferredThisType: String? = null
) :
// Important: use closureScope.thisObj so unqualified members (e.g., fields) resolve to the instance // Important: use closureScope.thisObj so unqualified members (e.g., fields) resolve to the instance
// we captured, not to the caller's `this` (e.g., FlowBuilder). // we captured, not to the caller's `this` (e.g., FlowBuilder).
Scope(callScope, callScope.args, thisObj = closureScope.thisObj) { Scope(callScope, callScope.args, thisObj = closureScope.thisObj) {
init { init {
val desired = preferredThisType?.let { typeName ->
callScope.thisVariants.firstOrNull { it.objClass.className == typeName }
}
val primaryThis = closureScope.thisObj
val merged = ArrayList<Obj>(callScope.thisVariants.size + closureScope.thisVariants.size + 1)
desired?.let { merged.add(it) }
merged.addAll(callScope.thisVariants)
merged.addAll(closureScope.thisVariants)
setThisVariants(primaryThis, merged)
// Preserve the lexical class context of the closure by default. This ensures that lambdas // Preserve the lexical class context of the closure by default. This ensures that lambdas
// created inside a class method keep access to that class's private/protected members even // created inside a class method keep access to that class's private/protected members even
// when executed from within another object's method (e.g., Mutex.withLock), which may set // when executed from within another object's method (e.g., Mutex.withLock), which may set
@ -72,14 +85,14 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
} }
class ApplyScope(val callScope: Scope, val applied: Scope) : class ApplyScope(val callScope: Scope, val applied: Scope) :
Scope(callScope.parent?.parent ?: callScope.parent ?: callScope, thisObj = applied.thisObj) { Scope(callScope, thisObj = applied.thisObj) {
override fun get(name: String): ObjRecord? { override fun get(name: String): ObjRecord? {
return applied.get(name) ?: super.get(name) return applied.get(name) ?: super.get(name)
} }
override fun applyClosure(closure: Scope): Scope { override fun applyClosure(closure: Scope, preferredThisType: String?): Scope {
return this return ClosureScope(this, closure, preferredThisType)
} }
} }

View File

@ -19,10 +19,19 @@ package net.sergeych.lyng
sealed class CodeContext { sealed class CodeContext {
class Module(@Suppress("unused") val packageName: String?): CodeContext() class Module(@Suppress("unused") val packageName: String?): CodeContext()
class Function(val name: String, val implicitThisMembers: Boolean = false): CodeContext() class Function(
val name: String,
val implicitThisMembers: Boolean = false,
val implicitThisTypeName: String? = null
): CodeContext()
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() { class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
val pendingInitializations = mutableMapOf<String, Pos>() val pendingInitializations = mutableMapOf<String, Pos>()
val declaredMembers = mutableSetOf<String>() val declaredMembers = mutableSetOf<String>()
val memberOverrides = mutableMapOf<String, Boolean>()
val memberFieldIds = mutableMapOf<String, Int>()
val memberMethodIds = mutableMapOf<String, Int>()
var nextFieldId: Int = 0
var nextMethodId: Int = 0
var slotPlanId: Int? = null var slotPlanId: Int? = null
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,34 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng
internal fun extensionCallableName(typeName: String, memberName: String): String {
return "__ext__${sanitizeExtensionTypeName(typeName)}__${memberName}"
}
internal fun extensionPropertyGetterName(typeName: String, memberName: String): String {
return "__ext_get__${sanitizeExtensionTypeName(typeName)}__${memberName}"
}
internal fun extensionPropertySetterName(typeName: String, memberName: String): String {
return "__ext_set__${sanitizeExtensionTypeName(typeName)}__${memberName}"
}
private fun sanitizeExtensionTypeName(typeName: String): String {
return typeName.replace('.', '_')
}

View File

@ -18,6 +18,8 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjExtensionPropertyGetterCallable
import net.sergeych.lyng.obj.ObjExtensionPropertySetterCallable
import net.sergeych.lyng.obj.ObjProperty import net.sergeych.lyng.obj.ObjProperty
import net.sergeych.lyng.obj.ObjRecord import net.sergeych.lyng.obj.ObjRecord
@ -45,6 +47,14 @@ class ExtensionPropertyDeclStatement(
type = ObjRecord.Type.Property type = ObjRecord.Type.Property
) )
) )
val getterName = extensionPropertyGetterName(extTypeName, property.name)
val getterWrapper = ObjExtensionPropertyGetterCallable(property.name, property)
context.addItem(getterName, false, getterWrapper, visibility, recordType = ObjRecord.Type.Fun)
if (property.setter != null) {
val setterName = extensionPropertySetterName(extTypeName, property.name)
val setterWrapper = ObjExtensionPropertySetterCallable(property.name, property)
context.addItem(setterName, false, setterWrapper, visibility, recordType = ObjRecord.Type.Fun)
}
return property return property
} }
} }

View File

@ -0,0 +1,37 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjVoid
class InlineBlockStatement(
private val statements: List<Statement>,
private val startPos: Pos,
) : Statement() {
override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj {
var last: Obj = ObjVoid
for (stmt in statements) {
last = stmt.execute(scope)
}
return last
}
fun statements(): List<Statement> = statements
}

View File

@ -52,6 +52,8 @@ open class Scope(
var currentClassCtx: net.sergeych.lyng.obj.ObjClass? = parent?.currentClassCtx var currentClassCtx: net.sergeych.lyng.obj.ObjClass? = parent?.currentClassCtx
// Unique id per scope frame for PICs; regenerated on each borrow from the pool. // Unique id per scope frame for PICs; regenerated on each borrow from the pool.
var frameId: Long = nextFrameId() var frameId: Long = nextFrameId()
@PublishedApi
internal val thisVariants: MutableList<Obj> = mutableListOf()
// Fast-path storage for local variables/arguments accessed by slot index. // Fast-path storage for local variables/arguments accessed by slot index.
// Enabled by default for child scopes; module/class scopes can ignore it. // Enabled by default for child scopes; module/class scopes can ignore it.
@ -66,6 +68,21 @@ open class Scope(
internal val extensions: MutableMap<ObjClass, MutableMap<String, ObjRecord>> = mutableMapOf() internal val extensions: MutableMap<ObjClass, MutableMap<String, ObjRecord>> = mutableMapOf()
init {
setThisVariants(thisObj, parent?.thisVariants ?: emptyList())
}
internal fun setThisVariants(primary: Obj, extras: List<Obj>) {
thisObj = primary
thisVariants.clear()
thisVariants.add(primary)
for (obj in extras) {
if (obj !== primary && !thisVariants.contains(obj)) {
thisVariants.add(obj)
}
}
}
fun addExtension(cls: ObjClass, name: String, record: ObjRecord) { fun addExtension(cls: ObjClass, name: String, record: ObjRecord) {
extensions.getOrPut(cls) { mutableMapOf() }[name] = record extensions.getOrPut(cls) { mutableMapOf() }[name] = record
} }
@ -340,11 +357,8 @@ open class Scope(
} }
inline fun <reified T : Obj> thisAs(): T { inline fun <reified T : Obj> thisAs(): T {
var s: Scope? = this for (obj in thisVariants) {
while (s != null) { if (obj is T) return obj
val t = s.thisObj
if (t is T) return t
s = s.parent
} }
raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}") raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
} }
@ -406,6 +420,8 @@ open class Scope(
get() = slots.size get() = slots.size
fun getSlotIndexOf(name: String): Int? = nameToSlot[name] fun getSlotIndexOf(name: String): Int? = nameToSlot[name]
internal fun slotNameToIndexSnapshot(): Map<String, Int> = nameToSlot.toMap()
fun allocateSlotFor(name: String, record: ObjRecord): Int { fun allocateSlotFor(name: String, record: ObjRecord): Int {
val idx = slots.size val idx = slots.size
slots.add(record) slots.add(record)
@ -490,6 +506,7 @@ open class Scope(
this.parent = null this.parent = null
this.skipScopeCreation = false this.skipScopeCreation = false
this.currentClassCtx = null this.currentClassCtx = null
thisVariants.clear()
objects.clear() objects.clear()
slots.clear() slots.clear()
nameToSlot.clear() nameToSlot.clear()
@ -520,7 +537,7 @@ open class Scope(
this.parent = parent this.parent = parent
this.args = args this.args = args
this.pos = pos this.pos = pos
this.thisObj = thisObj setThisVariants(thisObj, parent?.thisVariants ?: emptyList())
// Pre-size local slots for upcoming parameter assignment where possible // Pre-size local slots for upcoming parameter assignment where possible
reserveLocalCapacity(args.list.size + 4) reserveLocalCapacity(args.list.size + 4)
} }
@ -609,7 +626,10 @@ open class Scope(
isAbstract: Boolean = false, isAbstract: Boolean = false,
isClosed: Boolean = false, isClosed: Boolean = false,
isOverride: Boolean = false, isOverride: Boolean = false,
isTransient: Boolean = false isTransient: Boolean = false,
callSignature: CallSignature? = null,
fieldId: Int? = null,
methodId: Int? = null
): ObjRecord { ): ObjRecord {
val rec = ObjRecord( val rec = ObjRecord(
value, isMutable, visibility, writeVisibility, value, isMutable, visibility, writeVisibility,
@ -618,15 +638,19 @@ open class Scope(
isAbstract = isAbstract, isAbstract = isAbstract,
isClosed = isClosed, isClosed = isClosed,
isOverride = isOverride, isOverride = isOverride,
isTransient = isTransient isTransient = isTransient,
callSignature = callSignature,
memberName = name,
fieldId = fieldId,
methodId = methodId
) )
objects[name] = rec objects[name] = rec
bumpClassLayoutIfNeeded(name, value, recordType) bumpClassLayoutIfNeeded(name, value, recordType)
if (recordType == ObjRecord.Type.Field || recordType == ObjRecord.Type.ConstructorField) { if (recordType == ObjRecord.Type.Field || recordType == ObjRecord.Type.ConstructorField) {
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
if (inst != null) { if (inst != null) {
val slot = inst.objClass.fieldSlotForKey(name) val slotId = rec.fieldId ?: inst.objClass.fieldSlotForKey(name)?.slot
if (slot != null) inst.setFieldSlotRecord(slot.slot, rec) if (slotId != null) inst.setFieldSlotRecord(slotId, rec)
} }
} }
if (value is Statement || if (value is Statement ||
@ -635,8 +659,8 @@ open class Scope(
recordType == ObjRecord.Type.Property) { recordType == ObjRecord.Type.Property) {
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
if (inst != null) { if (inst != null) {
val slot = inst.objClass.methodSlotForKey(name) val slotId = rec.methodId ?: inst.objClass.methodSlotForKey(name)?.slot
if (slot != null) inst.setMethodSlotRecord(slot.slot, rec) if (slotId != null) inst.setMethodSlotRecord(slotId, rec)
} }
} }
// Index this binding within the current frame to help resolve locals across suspension // Index this binding within the current frame to help resolve locals across suspension
@ -697,7 +721,7 @@ open class Scope(
return CmdDisassembler.disassemble(bytecode) return CmdDisassembler.disassemble(bytecode)
} }
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) { fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend Scope.() -> Obj) {
val newFn = object : Statement() { val newFn = object : Statement() {
override val pos: Pos = Pos.builtIn override val pos: Pos = Pos.builtIn
@ -708,7 +732,9 @@ open class Scope(
addItem( addItem(
name, name,
false, false,
newFn newFn,
recordType = ObjRecord.Type.Fun,
callSignature = callSignature
) )
} }
} }
@ -778,7 +804,8 @@ open class Scope(
println("--------------------") println("--------------------")
} }
open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure) open fun applyClosure(closure: Scope, preferredThisType: String? = null): Scope =
ClosureScope(this, closure, preferredThisType)
/** /**
* Resolve and evaluate a qualified identifier exactly as compiled code would. * Resolve and evaluate a qualified identifier exactly as compiled code would.

View File

@ -428,8 +428,11 @@ class Script(
addConst("CompletableDeferred", ObjCompletableDeferred.type) addConst("CompletableDeferred", ObjCompletableDeferred.type)
addConst("Mutex", ObjMutex.type) addConst("Mutex", ObjMutex.type)
addConst("Flow", ObjFlow.type) addConst("Flow", ObjFlow.type)
addConst("FlowBuilder", ObjFlowBuilder.type)
addConst("Regex", ObjRegex.type) addConst("Regex", ObjRegex.type)
addConst("RegexMatch", ObjRegexMatch.type)
addConst("MapEntry", ObjMapEntry.type)
addFn("launch") { addFn("launch") {
val callable = requireOnlyArg<Statement>() val callable = requireOnlyArg<Statement>()
@ -443,7 +446,7 @@ class Script(
ObjVoid ObjVoid
} }
addFn("flow") { addFn("flow", callSignature = CallSignature(tailBlockReceiverType = "FlowBuilder")) {
// important is: current context contains closure often used in call; // important is: current context contains closure often used in call;
// we'll need it for the producer // we'll need it for the producer
ObjFlow(requireOnlyArg<Statement>(), this) ObjFlow(requireOnlyArg<Statement>(), this)

View File

@ -17,8 +17,10 @@
package net.sergeych.lyng package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjNull import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjRecord import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ObjUnset
class VarDeclStatement( class VarDeclStatement(
val name: String, val name: String,
@ -29,11 +31,12 @@ class VarDeclStatement(
val slotIndex: Int?, val slotIndex: Int?,
val scopeId: Int?, val scopeId: Int?,
private val startPos: Pos, private val startPos: Pos,
val initializerObjClass: ObjClass? = null,
) : Statement() { ) : Statement() {
override val pos: Pos = startPos override val pos: Pos = startPos
override suspend fun execute(context: Scope): Obj { override suspend fun execute(context: Scope): Obj {
val initValue = initializer?.execute(context)?.byValueCopy() ?: ObjNull val initValue = initializer?.execute(context)?.byValueCopy() ?: ObjUnset
context.addItem( context.addItem(
name, name,
isMutable, isMutable,

View File

@ -49,6 +49,7 @@ class BytecodeStatement private constructor(
allowLocalSlots: Boolean, allowLocalSlots: Boolean,
returnLabels: Set<String> = emptySet(), returnLabels: Set<String> = emptySet(),
rangeLocalNames: Set<String> = emptySet(), rangeLocalNames: Set<String> = emptySet(),
allowedScopeNames: Set<String>? = null,
): Statement { ): Statement {
if (statement is BytecodeStatement) return statement if (statement is BytecodeStatement) return statement
val hasUnsupported = containsUnsupportedStatement(statement) val hasUnsupported = containsUnsupportedStatement(statement)
@ -63,7 +64,8 @@ class BytecodeStatement private constructor(
val compiler = BytecodeCompiler( val compiler = BytecodeCompiler(
allowLocalSlots = safeLocals, allowLocalSlots = safeLocals,
returnLabels = returnLabels, returnLabels = returnLabels,
rangeLocalNames = rangeLocalNames rangeLocalNames = rangeLocalNames,
allowedScopeNames = allowedScopeNames
) )
val compiled = compiler.compileStatement(nameHint, statement) val compiled = compiler.compileStatement(nameHint, statement)
val fn = compiled ?: throw BytecodeFallbackException( val fn = compiled ?: throw BytecodeFallbackException(
@ -76,7 +78,14 @@ class BytecodeStatement private constructor(
private fun containsUnsupportedStatement(stmt: Statement): Boolean { private fun containsUnsupportedStatement(stmt: Statement): Boolean {
val target = if (stmt is BytecodeStatement) stmt.original else stmt val target = if (stmt is BytecodeStatement) stmt.original else stmt
return when (target) { return when (target) {
is net.sergeych.lyng.ExpressionStatement -> false is net.sergeych.lyng.ExpressionStatement -> {
val ref = target.ref
if (ref is net.sergeych.lyng.obj.StatementRef) {
containsUnsupportedStatement(ref.statement)
} else {
false
}
}
is net.sergeych.lyng.IfStatement -> { is net.sergeych.lyng.IfStatement -> {
containsUnsupportedStatement(target.condition) || containsUnsupportedStatement(target.condition) ||
containsUnsupportedStatement(target.ifBody) || containsUnsupportedStatement(target.ifBody) ||
@ -100,6 +109,8 @@ class BytecodeStatement private constructor(
} }
is net.sergeych.lyng.BlockStatement -> is net.sergeych.lyng.BlockStatement ->
target.statements().any { containsUnsupportedStatement(it) } target.statements().any { containsUnsupportedStatement(it) }
is net.sergeych.lyng.InlineBlockStatement ->
target.statements().any { containsUnsupportedStatement(it) }
is net.sergeych.lyng.VarDeclStatement -> is net.sergeych.lyng.VarDeclStatement ->
target.initializer?.let { containsUnsupportedStatement(it) } ?: false target.initializer?.let { containsUnsupportedStatement(it) } ?: false
is net.sergeych.lyng.DelegatedVarDeclStatement -> is net.sergeych.lyng.DelegatedVarDeclStatement ->
@ -117,7 +128,7 @@ class BytecodeStatement private constructor(
is net.sergeych.lyng.ClassDeclStatement -> false is net.sergeych.lyng.ClassDeclStatement -> false
is net.sergeych.lyng.FunctionDeclStatement -> false is net.sergeych.lyng.FunctionDeclStatement -> false
is net.sergeych.lyng.EnumDeclStatement -> false is net.sergeych.lyng.EnumDeclStatement -> false
is net.sergeych.lyng.TryStatement -> false is net.sergeych.lyng.TryStatement -> true
is net.sergeych.lyng.WhenStatement -> { is net.sergeych.lyng.WhenStatement -> {
containsUnsupportedStatement(target.value) || containsUnsupportedStatement(target.value) ||
target.cases.any { case -> target.cases.any { case ->
@ -151,7 +162,8 @@ class BytecodeStatement private constructor(
stmt.isTransient, stmt.isTransient,
stmt.slotIndex, stmt.slotIndex,
stmt.scopeId, stmt.scopeId,
stmt.pos stmt.pos,
stmt.initializerObjClass
) )
} }
is net.sergeych.lyng.DestructuringVarDeclStatement -> { is net.sergeych.lyng.DestructuringVarDeclStatement -> {

View File

@ -137,7 +137,7 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.ADDR) listOf(OperandKind.SLOT, OperandKind.ADDR)
Opcode.CONST_NULL -> Opcode.CONST_NULL ->
listOf(OperandKind.SLOT) listOf(OperandKind.SLOT)
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL -> Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL, Opcode.MAKE_VALUE_FN ->
listOf(OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN -> Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
listOf(OperandKind.CONST) listOf(OperandKind.CONST)
@ -161,14 +161,18 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.ASSIGN_OP_OBJ -> Opcode.ASSIGN_OP_OBJ ->
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST) listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST)
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET -> Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.LOAD_THIS ->
listOf(OperandKind.SLOT) listOf(OperandKind.SLOT)
Opcode.LOAD_THIS_VARIANT ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.JMP -> Opcode.JMP ->
listOf(OperandKind.IP) listOf(OperandKind.IP)
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE -> Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
listOf(OperandKind.SLOT, OperandKind.IP) listOf(OperandKind.SLOT, OperandKind.IP)
Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK -> Opcode.CALL_DIRECT ->
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_MEMBER_SLOT ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_SLOT -> Opcode.CALL_SLOT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_VIRTUAL -> Opcode.CALL_VIRTUAL ->
@ -177,8 +181,6 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
Opcode.SET_FIELD -> Opcode.SET_FIELD ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
Opcode.GET_NAME ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.GET_INDEX -> Opcode.GET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX -> Opcode.SET_INDEX ->
@ -187,12 +189,10 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_LITERAL -> Opcode.LIST_LITERAL ->
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_THIS_MEMBER -> Opcode.GET_MEMBER_SLOT ->
listOf(OperandKind.ID, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
Opcode.SET_THIS_MEMBER -> Opcode.SET_MEMBER_SLOT ->
listOf(OperandKind.ID, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.ITER_PUSH -> Opcode.ITER_PUSH ->
listOf(OperandKind.SLOT) listOf(OperandKind.SLOT)
Opcode.ITER_POP, Opcode.ITER_CANCEL -> Opcode.ITER_POP, Opcode.ITER_CANCEL ->
@ -229,9 +229,12 @@ class CmdBuilder {
Opcode.CONST_REAL -> CmdConstReal(operands[0], operands[1]) Opcode.CONST_REAL -> CmdConstReal(operands[0], operands[1])
Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1]) Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1])
Opcode.CONST_NULL -> CmdConstNull(operands[0]) Opcode.CONST_NULL -> CmdConstNull(operands[0])
Opcode.MAKE_VALUE_FN -> CmdMakeValueFn(operands[0], operands[1])
Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1]) Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1])
Opcode.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1]) Opcode.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3]) Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3])
Opcode.LOAD_THIS -> CmdLoadThis(operands[0])
Opcode.LOAD_THIS_VARIANT -> CmdLoadThisVariant(operands[0], operands[1])
Opcode.MAKE_RANGE -> CmdMakeRange(operands[0], operands[1], operands[2], operands[3]) Opcode.MAKE_RANGE -> CmdMakeRange(operands[0], operands[1], operands[2], operands[3])
Opcode.CHECK_IS -> CmdCheckIs(operands[0], operands[1], operands[2]) Opcode.CHECK_IS -> CmdCheckIs(operands[0], operands[1], operands[2])
Opcode.ASSERT_IS -> CmdAssertIs(operands[0], operands[1]) Opcode.ASSERT_IS -> CmdAssertIs(operands[0], operands[1])
@ -378,21 +381,16 @@ class CmdBuilder {
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1]) Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1]) Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3]) Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
Opcode.CALL_MEMBER_SLOT -> CmdCallMemberSlot(operands[0], operands[1], operands[2], operands[3], operands[4])
Opcode.CALL_VIRTUAL -> CmdCallVirtual(operands[0], operands[1], operands[2], operands[3], operands[4]) Opcode.CALL_VIRTUAL -> CmdCallVirtual(operands[0], operands[1], operands[2], operands[3], operands[4])
Opcode.CALL_FALLBACK -> CmdCallFallback(operands[0], operands[1], operands[2], operands[3])
Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3]) Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3])
Opcode.GET_FIELD -> CmdGetField(operands[0], operands[1], operands[2]) Opcode.GET_FIELD -> CmdGetField(operands[0], operands[1], operands[2])
Opcode.SET_FIELD -> CmdSetField(operands[0], operands[1], operands[2]) Opcode.SET_FIELD -> CmdSetField(operands[0], operands[1], operands[2])
Opcode.GET_NAME -> CmdGetName(operands[0], operands[1])
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2]) Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2]) Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3]) Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
Opcode.GET_THIS_MEMBER -> CmdGetThisMember(operands[0], operands[1]) Opcode.GET_MEMBER_SLOT -> CmdGetMemberSlot(operands[0], operands[1], operands[2], operands[3])
Opcode.SET_THIS_MEMBER -> CmdSetThisMember(operands[0], operands[1]) Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3])
Opcode.EVAL_FALLBACK -> CmdEvalFallback(operands[0], operands[1])
Opcode.EVAL_REF -> CmdEvalRef(operands[0], operands[1])
Opcode.EVAL_STMT -> CmdEvalStmt(operands[0], operands[1])
Opcode.EVAL_VALUE_FN -> CmdEvalValueFn(operands[0], operands[1])
Opcode.ITER_PUSH -> CmdIterPush(operands[0]) Opcode.ITER_PUSH -> CmdIterPush(operands[0])
Opcode.ITER_POP -> CmdIterPop() Opcode.ITER_POP -> CmdIterPop()
Opcode.ITER_CANCEL -> CmdIterCancel() Opcode.ITER_CANCEL -> CmdIterCancel()

View File

@ -66,7 +66,10 @@ object CmdDisassembler {
is CmdConstIntLocal -> Opcode.CONST_INT to intArrayOf(cmd.constId, cmd.dst + fn.scopeSlotCount) is CmdConstIntLocal -> Opcode.CONST_INT to intArrayOf(cmd.constId, cmd.dst + fn.scopeSlotCount)
is CmdConstReal -> Opcode.CONST_REAL to intArrayOf(cmd.constId, cmd.dst) is CmdConstReal -> Opcode.CONST_REAL to intArrayOf(cmd.constId, cmd.dst)
is CmdConstBool -> Opcode.CONST_BOOL to intArrayOf(cmd.constId, cmd.dst) is CmdConstBool -> Opcode.CONST_BOOL to intArrayOf(cmd.constId, cmd.dst)
is CmdLoadThis -> Opcode.LOAD_THIS to intArrayOf(cmd.dst)
is CmdLoadThisVariant -> Opcode.LOAD_THIS_VARIANT to intArrayOf(cmd.typeId, cmd.dst)
is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst) is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst)
is CmdMakeValueFn -> Opcode.MAKE_VALUE_FN to intArrayOf(cmd.id, cmd.dst)
is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst) is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst)
is CmdObjToBool -> Opcode.OBJ_TO_BOOL to intArrayOf(cmd.src, cmd.dst) is CmdObjToBool -> Opcode.OBJ_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst) is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
@ -178,23 +181,19 @@ object CmdDisassembler {
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot) is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst) is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
is CmdCallVirtual -> Opcode.CALL_VIRTUAL to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst) is CmdCallVirtual -> Opcode.CALL_VIRTUAL to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
is CmdCallFallback -> Opcode.CALL_FALLBACK to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst) is CmdCallMemberSlot -> Opcode.CALL_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst) is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
is CmdGetField -> Opcode.GET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.dst) is CmdGetField -> Opcode.GET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.dst)
is CmdSetField -> Opcode.SET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.valueSlot) is CmdSetField -> Opcode.SET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.valueSlot)
is CmdGetName -> Opcode.GET_NAME to intArrayOf(cmd.nameId, cmd.dst)
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst) is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot) is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst) is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
is CmdGetThisMember -> Opcode.GET_THIS_MEMBER to intArrayOf(cmd.nameId, cmd.dst) is CmdGetMemberSlot -> Opcode.GET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.dst)
is CmdSetThisMember -> Opcode.SET_THIS_MEMBER to intArrayOf(cmd.nameId, cmd.valueSlot) is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot)
is CmdEvalFallback -> Opcode.EVAL_FALLBACK to intArrayOf(cmd.id, cmd.dst)
is CmdEvalRef -> Opcode.EVAL_REF to intArrayOf(cmd.id, cmd.dst)
is CmdEvalStmt -> Opcode.EVAL_STMT to intArrayOf(cmd.id, cmd.dst)
is CmdEvalValueFn -> Opcode.EVAL_VALUE_FN to intArrayOf(cmd.id, cmd.dst)
is CmdIterPush -> Opcode.ITER_PUSH to intArrayOf(cmd.iterSlot) is CmdIterPush -> Opcode.ITER_PUSH to intArrayOf(cmd.iterSlot)
is CmdIterPop -> Opcode.ITER_POP to intArrayOf() is CmdIterPop -> Opcode.ITER_POP to intArrayOf()
is CmdIterCancel -> Opcode.ITER_CANCEL to intArrayOf() is CmdIterCancel -> Opcode.ITER_CANCEL to intArrayOf()
else -> error("Unsupported cmd in disassembler: ${cmd::class.simpleName}")
} }
} }
@ -231,7 +230,7 @@ object CmdDisassembler {
listOf(OperandKind.SLOT, OperandKind.ADDR) listOf(OperandKind.SLOT, OperandKind.ADDR)
Opcode.CONST_NULL -> Opcode.CONST_NULL ->
listOf(OperandKind.SLOT) listOf(OperandKind.SLOT)
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL -> Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL, Opcode.MAKE_VALUE_FN ->
listOf(OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN -> Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
listOf(OperandKind.CONST) listOf(OperandKind.CONST)
@ -255,36 +254,36 @@ object CmdDisassembler {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.ASSIGN_OP_OBJ -> Opcode.ASSIGN_OP_OBJ ->
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST) listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST)
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.ITER_PUSH -> Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.ITER_PUSH, Opcode.LOAD_THIS ->
listOf(OperandKind.SLOT) listOf(OperandKind.SLOT)
Opcode.LOAD_THIS_VARIANT ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.JMP -> Opcode.JMP ->
listOf(OperandKind.IP) listOf(OperandKind.IP)
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE -> Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
listOf(OperandKind.SLOT, OperandKind.IP) listOf(OperandKind.SLOT, OperandKind.IP)
Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK -> Opcode.CALL_DIRECT ->
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_SLOT -> Opcode.CALL_SLOT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_VIRTUAL -> Opcode.CALL_VIRTUAL ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_MEMBER_SLOT ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_FIELD -> Opcode.GET_FIELD ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
Opcode.SET_FIELD -> Opcode.SET_FIELD ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
Opcode.GET_NAME ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.GET_INDEX -> Opcode.GET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX -> Opcode.SET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_LITERAL -> Opcode.LIST_LITERAL ->
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_THIS_MEMBER -> Opcode.GET_MEMBER_SLOT ->
listOf(OperandKind.ID, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
Opcode.SET_THIS_MEMBER -> Opcode.SET_MEMBER_SLOT ->
listOf(OperandKind.ID, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN ->
listOf(OperandKind.ID, OperandKind.SLOT)
} }
} }
} }

View File

@ -152,6 +152,28 @@ class CmdConstBool(internal val constId: Int, internal val dst: Int) : Cmd() {
} }
} }
class CmdLoadThis(internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setObj(dst, frame.scope.thisObj)
return
}
}
class CmdLoadThisVariant(
internal val typeId: Int,
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val typeConst = frame.fn.constants.getOrNull(typeId) as? BytecodeConst.StringVal
?: error("LOAD_THIS_VARIANT expects StringVal at $typeId")
val typeName = typeConst.value
val receiver = frame.scope.thisVariants.firstOrNull { it.isInstanceOf(typeName) }
?: frame.scope.raiseClassCastError("Cannot cast ${frame.scope.thisObj.objClass.className} to $typeName")
frame.setObj(dst, receiver)
return
}
}
class CmdMakeRange( class CmdMakeRange(
internal val startSlot: Int, internal val startSlot: Int,
internal val endSlot: Int, internal val endSlot: Int,
@ -1146,20 +1168,7 @@ class CmdCallVirtual(
internal val dst: Int, internal val dst: Int,
) : Cmd() { ) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) { frame.scope.raiseError("CALL_VIRTUAL is not allowed: compile-time member resolution is required")
frame.syncFrameToScope()
}
val receiver = frame.slotToObj(recvSlot)
val nameConst = frame.fn.constants.getOrNull(methodId) as? BytecodeConst.StringVal
?: error("CALL_VIRTUAL expects StringVal at $methodId")
val args = frame.buildArguments(argBase, argCount)
val site = frame.methodCallSites.getOrPut(frame.ip - 1) { MethodCallSite(nameConst.value) }
val result = site.invoke(frame.scope, receiver, args)
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
}
frame.storeObjResult(dst, result)
return
} }
} }
@ -1313,38 +1322,87 @@ class CmdListLiteral(
} }
} }
class CmdGetThisMember( class CmdGetMemberSlot(
internal val nameId: Int, internal val recvSlot: Int,
internal val fieldId: Int,
internal val methodId: Int,
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val receiver = frame.slotToObj(recvSlot)
val inst = receiver as? ObjInstance
val fieldRec = if (fieldId >= 0) {
inst?.fieldRecordForId(fieldId) ?: receiver.objClass.fieldRecordForId(fieldId)
} else null
val rec = fieldRec ?: run {
if (methodId >= 0) {
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
} else null
} ?: frame.scope.raiseSymbolNotFound("member")
val name = rec.memberName ?: "<member>"
val resolved = receiver.resolveRecord(frame.scope, rec, name, rec.declaringClass)
frame.storeObjResult(dst, resolved.value)
return
}
}
class CmdSetMemberSlot(
internal val recvSlot: Int,
internal val fieldId: Int,
internal val methodId: Int,
internal val valueSlot: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val receiver = frame.slotToObj(recvSlot)
val inst = receiver as? ObjInstance
val fieldRec = if (fieldId >= 0) {
inst?.fieldRecordForId(fieldId) ?: receiver.objClass.fieldRecordForId(fieldId)
} else null
val rec = fieldRec ?: run {
if (methodId >= 0) {
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
} else null
} ?: frame.scope.raiseSymbolNotFound("member")
val name = rec.memberName ?: "<member>"
frame.scope.assign(rec, name, frame.slotToObj(valueSlot))
return
}
}
class CmdCallMemberSlot(
internal val recvSlot: Int,
internal val methodId: Int,
internal val argBase: Int,
internal val argCount: Int,
internal val dst: Int, internal val dst: Int,
) : Cmd() { ) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope() frame.syncFrameToScope()
} }
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal val receiver = frame.slotToObj(recvSlot)
?: error("GET_THIS_MEMBER expects StringVal at $nameId") val inst = receiver as? ObjInstance
val ref = net.sergeych.lyng.obj.ImplicitThisMemberRef(nameConst.value, frame.scope.pos) val rec = inst?.methodRecordForId(methodId)
val result = ref.evalValue(frame.scope) ?: receiver.objClass.methodRecordForId(methodId)
frame.storeObjResult(dst, result) ?: frame.scope.raiseError("member id $methodId not found on ${receiver.objClass.className}")
return val callArgs = frame.buildArguments(argBase, argCount)
val name = rec.memberName ?: "<member>"
val decl = rec.declaringClass ?: receiver.objClass
val result = when (rec.type) {
ObjRecord.Type.Property -> {
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(frame.scope, receiver, decl)
else frame.scope.raiseError("property $name cannot be called with arguments")
} }
} ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> {
val callScope = inst?.instanceScope ?: frame.scope
class CmdSetThisMember( rec.value.invoke(callScope, receiver, callArgs, decl)
internal val nameId: Int, }
internal val valueSlot: Int, else -> frame.scope.raiseError("member $name is not callable")
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope()
} }
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal
?: error("SET_THIS_MEMBER expects StringVal at $nameId")
val ref = net.sergeych.lyng.obj.ImplicitThisMemberRef(nameConst.value, frame.scope.pos)
ref.setAt(frame.scope.pos, frame.scope, frame.slotToObj(valueSlot))
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame() frame.syncScopeToFrame()
} }
frame.storeObjResult(dst, result)
return return
} }
} }
@ -1449,13 +1507,13 @@ class CmdEvalStmt(internal val id: Int, internal val dst: Int) : Cmd() {
} }
} }
class CmdEvalValueFn(internal val id: Int, internal val dst: Int) : Cmd() { class CmdMakeValueFn(internal val id: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope() frame.syncFrameToScope()
} }
val valueFn = frame.fn.constants.getOrNull(id) as? BytecodeConst.ValueFn val valueFn = frame.fn.constants.getOrNull(id) as? BytecodeConst.ValueFn
?: error("EVAL_VALUE_FN expects ValueFn at $id") ?: error("MAKE_VALUE_FN expects ValueFn at $id")
val result = valueFn.fn(frame.scope).value val result = valueFn.fn(frame.scope).value
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame() frame.syncScopeToFrame()

View File

@ -30,6 +30,9 @@ enum class Opcode(val code: Int) {
BOX_OBJ(0x0A), BOX_OBJ(0x0A),
RANGE_INT_BOUNDS(0x0B), RANGE_INT_BOUNDS(0x0B),
MAKE_RANGE(0x0C), MAKE_RANGE(0x0C),
LOAD_THIS(0x0D),
MAKE_VALUE_FN(0x0E),
LOAD_THIS_VARIANT(0x0F),
INT_TO_REAL(0x10), INT_TO_REAL(0x10),
REAL_TO_INT(0x11), REAL_TO_INT(0x11),
@ -124,19 +127,17 @@ enum class Opcode(val code: Int) {
CALL_DIRECT(0x90), CALL_DIRECT(0x90),
CALL_VIRTUAL(0x91), CALL_VIRTUAL(0x91),
CALL_FALLBACK(0x92), CALL_MEMBER_SLOT(0x92),
CALL_SLOT(0x93), CALL_SLOT(0x93),
GET_FIELD(0xA0), GET_FIELD(0xA0),
SET_FIELD(0xA1), SET_FIELD(0xA1),
GET_INDEX(0xA2), GET_INDEX(0xA2),
SET_INDEX(0xA3), SET_INDEX(0xA3),
GET_NAME(0xA4),
LIST_LITERAL(0xA5), LIST_LITERAL(0xA5),
GET_THIS_MEMBER(0xA6), GET_MEMBER_SLOT(0xA8),
SET_THIS_MEMBER(0xA7), SET_MEMBER_SLOT(0xA9),
EVAL_FALLBACK(0xB0),
RESOLVE_SCOPE_SLOT(0xB1), RESOLVE_SCOPE_SLOT(0xB1),
LOAD_OBJ_ADDR(0xB2), LOAD_OBJ_ADDR(0xB2),
STORE_OBJ_ADDR(0xB3), STORE_OBJ_ADDR(0xB3),
@ -147,9 +148,6 @@ enum class Opcode(val code: Int) {
LOAD_BOOL_ADDR(0xB8), LOAD_BOOL_ADDR(0xB8),
STORE_BOOL_ADDR(0xB9), STORE_BOOL_ADDR(0xB9),
THROW(0xBB), THROW(0xBB),
EVAL_REF(0xBC),
EVAL_STMT(0xBD),
EVAL_VALUE_FN(0xBE),
ITER_PUSH(0xBF), ITER_PUSH(0xBF),
ITER_POP(0xC0), ITER_POP(0xC0),
ITER_CANCEL(0xC1), ITER_CANCEL(0xC1),

View File

@ -39,10 +39,11 @@ inline fun <reified T : Obj> Scope.addFnDoc(
returns: TypeDoc? = null, returns: TypeDoc? = null,
tags: Map<String, List<String>> = emptyMap(), tags: Map<String, List<String>> = emptyMap(),
moduleName: String? = null, moduleName: String? = null,
callSignature: net.sergeych.lyng.CallSignature? = null,
crossinline fn: suspend Scope.() -> T crossinline fn: suspend Scope.() -> T
) { ) {
// Register runtime function(s) // Register runtime function(s)
addFn(*names) { fn() } addFn(*names, callSignature = callSignature) { fn() }
// Determine module // Determine module
val mod = moduleName ?: findModuleNameOrUnknown() val mod = moduleName ?: findModuleNameOrUnknown()
// Register docs once per name // Register docs once per name

View File

@ -124,17 +124,7 @@ open class Obj {
} }
} }
// 2. Extensions in scope // 2. Root object fallback
val extension = scope.findExtension(objClass, name)
if (extension != null) {
if (extension.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (extension.value as ObjProperty).callGetter(scope, this, extension.declaringClass)
} else if (extension.type != ObjRecord.Type.Delegated) {
return extension.value.invoke(scope, this, args)
}
}
// 3. Root object fallback
for (cls in objClass.mro) { for (cls in objClass.mro) {
if (cls.className == "Obj") { if (cls.className == "Obj") {
cls.members[name]?.let { rec -> cls.members[name]?.let { rec ->
@ -181,7 +171,7 @@ open class Obj {
open suspend fun equals(scope: Scope, other: Obj): Boolean { open suspend fun equals(scope: Scope, other: Obj): Boolean {
if (other === this) return true if (other === this) return true
val m = objClass.getInstanceMemberOrNull("equals") ?: scope.findExtension(objClass, "equals") val m = objClass.getInstanceMemberOrNull("equals")
if (m != null) { if (m != null) {
return invokeInstanceMethod(scope, "equals", Arguments(other)).toBool() return invokeInstanceMethod(scope, "equals", Arguments(other)).toBool()
} }
@ -375,7 +365,7 @@ open class Obj {
* to generate it as 'this = this + other', reassigning its variable * to generate it as 'this = this + other', reassigning its variable
*/ */
open suspend fun plusAssign(scope: Scope, other: Obj): Obj? { open suspend fun plusAssign(scope: Scope, other: Obj): Obj? {
val m = objClass.getInstanceMemberOrNull("plusAssign") ?: scope.findExtension(objClass, "plusAssign") val m = objClass.getInstanceMemberOrNull("plusAssign")
return if (m != null) { return if (m != null) {
invokeInstanceMethod(scope, "plusAssign", Arguments(other)) invokeInstanceMethod(scope, "plusAssign", Arguments(other))
} else null } else null
@ -385,28 +375,28 @@ open class Obj {
* `-=` operations, see [plusAssign] * `-=` operations, see [plusAssign]
*/ */
open suspend fun minusAssign(scope: Scope, other: Obj): Obj? { open suspend fun minusAssign(scope: Scope, other: Obj): Obj? {
val m = objClass.getInstanceMemberOrNull("minusAssign") ?: scope.findExtension(objClass, "minusAssign") val m = objClass.getInstanceMemberOrNull("minusAssign")
return if (m != null) { return if (m != null) {
invokeInstanceMethod(scope, "minusAssign", Arguments(other)) invokeInstanceMethod(scope, "minusAssign", Arguments(other))
} else null } else null
} }
open suspend fun mulAssign(scope: Scope, other: Obj): Obj? { open suspend fun mulAssign(scope: Scope, other: Obj): Obj? {
val m = objClass.getInstanceMemberOrNull("mulAssign") ?: scope.findExtension(objClass, "mulAssign") val m = objClass.getInstanceMemberOrNull("mulAssign")
return if (m != null) { return if (m != null) {
invokeInstanceMethod(scope, "mulAssign", Arguments(other)) invokeInstanceMethod(scope, "mulAssign", Arguments(other))
} else null } else null
} }
open suspend fun divAssign(scope: Scope, other: Obj): Obj? { open suspend fun divAssign(scope: Scope, other: Obj): Obj? {
val m = objClass.getInstanceMemberOrNull("divAssign") ?: scope.findExtension(objClass, "divAssign") val m = objClass.getInstanceMemberOrNull("divAssign")
return if (m != null) { return if (m != null) {
invokeInstanceMethod(scope, "divAssign", Arguments(other)) invokeInstanceMethod(scope, "divAssign", Arguments(other))
} else null } else null
} }
open suspend fun modAssign(scope: Scope, other: Obj): Obj? { open suspend fun modAssign(scope: Scope, other: Obj): Obj? {
val m = objClass.getInstanceMemberOrNull("modAssign") ?: scope.findExtension(objClass, "modAssign") val m = objClass.getInstanceMemberOrNull("modAssign")
return if (m != null) { return if (m != null) {
invokeInstanceMethod(scope, "modAssign", Arguments(other)) invokeInstanceMethod(scope, "modAssign", Arguments(other))
} else null } else null
@ -467,16 +457,7 @@ open class Obj {
} }
} }
// 2. Extensions // 2. Root fallback
val extension = scope.findExtension(objClass, name)
if (extension != null) {
val resolved = resolveRecord(scope, extension, name, extension.declaringClass)
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, extension.declaringClass))
return resolved
}
// 3. Root fallback
for (cls in objClass.mro) { for (cls in objClass.mro) {
if (cls.className == "Obj") { if (cls.className == "Obj") {
cls.members[name]?.let { rec -> cls.members[name]?.let { rec ->
@ -558,11 +539,7 @@ open class Obj {
} }
} }
} }
// 2. Extensions // 2. Root fallback
if (field == null) {
field = scope.findExtension(objClass, name)
}
// 3. Root fallback
if (field == null) { if (field == null) {
for (cls in objClass.mro) { for (cls in objClass.mro) {
if (cls.className == "Obj") { if (cls.className == "Obj") {

View File

@ -275,6 +275,11 @@ open class ObjClass(
internal data class FieldSlot(val slot: Int, val record: ObjRecord) internal data class FieldSlot(val slot: Int, val record: ObjRecord)
internal data class ResolvedMember(val record: ObjRecord, val declaringClass: ObjClass) internal data class ResolvedMember(val record: ObjRecord, val declaringClass: ObjClass)
internal data class MethodSlot(val slot: Int, val record: ObjRecord) internal data class MethodSlot(val slot: Int, val record: ObjRecord)
private var nextFieldId: Int = 0
private var nextMethodId: Int = 0
private val fieldIdMap: MutableMap<String, Int> = mutableMapOf()
private val methodIdMap: MutableMap<String, Int> = mutableMapOf()
private var methodIdSeeded: Boolean = false
private var fieldSlotLayoutVersion: Int = -1 private var fieldSlotLayoutVersion: Int = -1
private var fieldSlotMap: Map<String, FieldSlot> = emptyMap() private var fieldSlotMap: Map<String, FieldSlot> = emptyMap()
private var fieldSlotCount: Int = 0 private var fieldSlotCount: Int = 0
@ -287,19 +292,29 @@ open class ObjClass(
private fun ensureFieldSlots(): Map<String, FieldSlot> { private fun ensureFieldSlots(): Map<String, FieldSlot> {
if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap
val res = mutableMapOf<String, FieldSlot>() val res = mutableMapOf<String, FieldSlot>()
var idx = 0 var maxId = -1
for (cls in mro) { for (cls in mro) {
for ((name, rec) in cls.members) { for ((name, rec) in cls.members) {
if (rec.isAbstract) continue if (rec.isAbstract) continue
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
val key = cls.mangledName(name) val key = cls.mangledName(name)
if (res.containsKey(key)) continue if (res.containsKey(key)) continue
res[key] = FieldSlot(idx, rec) val fieldId = rec.fieldId ?: cls.assignFieldId(name, rec)
idx += 1 res[key] = FieldSlot(fieldId, rec)
if (fieldId > maxId) maxId = fieldId
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.isAbstract) return@forEach
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) return@forEach
val key = cls.mangledName(name)
if (res.containsKey(key)) return@forEach
val fieldId = rec.fieldId ?: cls.assignFieldId(name, rec)
res[key] = FieldSlot(fieldId, rec)
if (fieldId > maxId) maxId = fieldId
} }
} }
fieldSlotMap = res fieldSlotMap = res
fieldSlotCount = idx fieldSlotCount = maxId + 1
fieldSlotLayoutVersion = layoutVersion fieldSlotLayoutVersion = layoutVersion
return fieldSlotMap return fieldSlotMap
} }
@ -308,7 +323,6 @@ open class ObjClass(
if (instanceMemberLayoutVersion == layoutVersion) return instanceMemberCache if (instanceMemberLayoutVersion == layoutVersion) return instanceMemberCache
val res = mutableMapOf<String, ResolvedMember>() val res = mutableMapOf<String, ResolvedMember>()
for (cls in mro) { for (cls in mro) {
if (cls.className == "Obj") break
for ((name, rec) in cls.members) { for ((name, rec) in cls.members) {
if (rec.isAbstract) continue if (rec.isAbstract) continue
if (res.containsKey(name)) continue if (res.containsKey(name)) continue
@ -330,7 +344,7 @@ open class ObjClass(
private fun ensureMethodSlots(): Map<String, MethodSlot> { private fun ensureMethodSlots(): Map<String, MethodSlot> {
if (methodSlotLayoutVersion == layoutVersion) return methodSlotMap if (methodSlotLayoutVersion == layoutVersion) return methodSlotMap
val res = mutableMapOf<String, MethodSlot>() val res = mutableMapOf<String, MethodSlot>()
var idx = 0 var maxId = -1
for (cls in mro) { for (cls in mro) {
if (cls.className == "Obj") break if (cls.className == "Obj") break
for ((name, rec) in cls.members) { for ((name, rec) in cls.members) {
@ -343,8 +357,9 @@ open class ObjClass(
} }
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
if (res.containsKey(key)) continue if (res.containsKey(key)) continue
res[key] = MethodSlot(idx, rec) val methodId = rec.methodId ?: cls.assignMethodId(name, rec)
idx += 1 res[key] = MethodSlot(methodId, rec)
if (methodId > maxId) maxId = methodId
} }
cls.classScope?.objects?.forEach { (name, rec) -> cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.isAbstract) return@forEach if (rec.isAbstract) return@forEach
@ -353,12 +368,13 @@ open class ObjClass(
rec.type != ObjRecord.Type.Property) return@forEach rec.type != ObjRecord.Type.Property) return@forEach
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
if (res.containsKey(key)) return@forEach if (res.containsKey(key)) return@forEach
res[key] = MethodSlot(idx, rec) val methodId = rec.methodId ?: cls.assignMethodId(name, rec)
idx += 1 res[key] = MethodSlot(methodId, rec)
if (methodId > maxId) maxId = methodId
} }
} }
methodSlotMap = res methodSlotMap = res
methodSlotCount = idx methodSlotCount = maxId + 1
methodSlotLayoutVersion = layoutVersion methodSlotLayoutVersion = layoutVersion
return methodSlotMap return methodSlotMap
} }
@ -374,6 +390,10 @@ open class ObjClass(
} }
internal fun fieldSlotMap(): Map<String, FieldSlot> = ensureFieldSlots() internal fun fieldSlotMap(): Map<String, FieldSlot> = ensureFieldSlots()
internal fun fieldRecordForId(fieldId: Int): ObjRecord? {
ensureFieldSlots()
return fieldSlotMap.values.firstOrNull { it.slot == fieldId }?.record
}
internal fun resolveInstanceMember(name: String): ResolvedMember? = ensureInstanceMemberCache()[name] internal fun resolveInstanceMember(name: String): ResolvedMember? = ensureInstanceMemberCache()[name]
internal fun methodSlotCount(): Int { internal fun methodSlotCount(): Int {
ensureMethodSlots() ensureMethodSlots()
@ -384,6 +404,117 @@ open class ObjClass(
return methodSlotMap[key] return methodSlotMap[key]
} }
internal fun methodSlotMap(): Map<String, MethodSlot> = ensureMethodSlots() internal fun methodSlotMap(): Map<String, MethodSlot> = ensureMethodSlots()
internal fun methodRecordForId(methodId: Int): ObjRecord? {
ensureMethodSlots()
methodSlotMap.values.firstOrNull { it.slot == methodId }?.record?.let { return it }
// Fallback to scanning the MRO in case a parent method id was added after slot cache creation.
for (cls in mro) {
for ((_, rec) in cls.members) {
if (rec.methodId == methodId) return rec
}
cls.classScope?.objects?.forEach { (_, rec) ->
if (rec.methodId == methodId) return rec
}
}
return null
}
internal fun instanceFieldIdMap(): Map<String, Int> {
val result = mutableMapOf<String, Int>()
for (cls in mro) {
if (cls.className == "Obj") break
for ((name, rec) in cls.members) {
if (rec.isAbstract) continue
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
if (rec.visibility == Visibility.Private) continue
val id = rec.fieldId ?: cls.assignFieldId(name, rec)
result.putIfAbsent(name, id)
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.isAbstract) return@forEach
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) return@forEach
if (rec.visibility == Visibility.Private) return@forEach
val id = rec.fieldId ?: cls.assignFieldId(name, rec)
result.putIfAbsent(name, id)
}
}
return result
}
internal fun instanceMethodIdMap(includeAbstract: Boolean = false): Map<String, Int> {
val result = mutableMapOf<String, Int>()
for (cls in mro) {
for ((name, rec) in cls.members) {
if (!includeAbstract && rec.isAbstract) continue
if (rec.visibility == Visibility.Private) continue
if (rec.type != ObjRecord.Type.Fun &&
rec.type != ObjRecord.Type.Property &&
rec.type != ObjRecord.Type.Delegated) continue
val id = rec.methodId ?: cls.assignMethodId(name, rec)
result.putIfAbsent(name, id)
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (!includeAbstract && rec.isAbstract) return@forEach
if (rec.visibility == Visibility.Private) return@forEach
if (rec.type != ObjRecord.Type.Fun &&
rec.type != ObjRecord.Type.Property &&
rec.type != ObjRecord.Type.Delegated) return@forEach
val id = rec.methodId ?: cls.assignMethodId(name, rec)
result.putIfAbsent(name, id)
}
}
return result
}
private fun assignFieldId(name: String, rec: ObjRecord): Int {
val existingId = rec.fieldId
if (existingId != null) {
fieldIdMap[name] = existingId
return existingId
}
val id = fieldIdMap.getOrPut(name) { nextFieldId++ }
return id
}
private fun assignMethodId(name: String, rec: ObjRecord): Int {
ensureMethodIdSeeded()
val existingId = rec.methodId
if (existingId != null) {
methodIdMap[name] = existingId
return existingId
}
val id = methodIdMap.getOrPut(name) { nextMethodId++ }
return id
}
private fun ensureMethodIdSeeded() {
if (methodIdSeeded) return
var maxId = -1
for (cls in mroParents) {
for ((name, rec) in cls.members) {
if (rec.type != ObjRecord.Type.Fun &&
rec.type != ObjRecord.Type.Property &&
rec.type != ObjRecord.Type.Delegated
) continue
val id = rec.methodId ?: cls.assignMethodId(name, rec)
methodIdMap.putIfAbsent(name, id)
if (id > maxId) maxId = id
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.type != ObjRecord.Type.Fun &&
rec.type != ObjRecord.Type.Property &&
rec.type != ObjRecord.Type.Delegated
) return@forEach
val id = rec.methodId ?: cls.assignMethodId(name, rec)
methodIdMap.putIfAbsent(name, id)
if (id > maxId) maxId = id
}
}
if (nextMethodId <= maxId) {
nextMethodId = maxId + 1
}
methodIdSeeded = true
}
override fun toString(): String = className override fun toString(): String = className
@ -618,12 +749,15 @@ open class ObjClass(
isOverride: Boolean = false, isOverride: Boolean = false,
isTransient: Boolean = false, isTransient: Boolean = false,
type: ObjRecord.Type = ObjRecord.Type.Field, type: ObjRecord.Type = ObjRecord.Type.Field,
fieldId: Int? = null,
methodId: Int? = null,
): ObjRecord { ): ObjRecord {
// Validation of override rules: only for non-system declarations // Validation of override rules: only for non-system declarations
var existing: ObjRecord? = null
var actualOverride = false
if (pos != Pos.builtIn) { if (pos != Pos.builtIn) {
// Only consider TRUE instance members from ancestors for overrides // Only consider TRUE instance members from ancestors for overrides
val existing = getInstanceMemberOrNull(name, includeAbstract = true, includeStatic = false) existing = getInstanceMemberOrNull(name, includeAbstract = true, includeStatic = false)
var actualOverride = false
if (existing != null && existing.declaringClass != this) { if (existing != null && existing.declaringClass != this) {
// If the existing member is private in the ancestor, it's not visible for overriding. // If the existing member is private in the ancestor, it's not visible for overriding.
// It should be treated as a new member in this class. // It should be treated as a new member in this class.
@ -654,6 +788,56 @@ open class ObjClass(
throw ScriptError(pos, "$name is already defined in $objClass") throw ScriptError(pos, "$name is already defined in $objClass")
// Install/override in this class // Install/override in this class
val effectiveFieldId = if (type == ObjRecord.Type.Field || type == ObjRecord.Type.ConstructorField) {
fieldId ?: fieldIdMap[name]?.let { it } ?: run {
fieldIdMap[name] = nextFieldId
nextFieldId++
fieldIdMap[name]!!
}
} else {
fieldId
}
val inheritedCandidate = run {
var found: ObjRecord? = null
for (cls in mro) {
if (cls === this) continue
if (cls.className == "Obj") break
cls.members[name]?.let {
found = it
return@run found
}
}
found
}
if (type == ObjRecord.Type.Fun ||
type == ObjRecord.Type.Property ||
type == ObjRecord.Type.Delegated
) {
ensureMethodIdSeeded()
}
val effectiveMethodId = if (type == ObjRecord.Type.Fun ||
type == ObjRecord.Type.Property ||
type == ObjRecord.Type.Delegated
) {
val inherited = if (actualOverride) {
existing?.methodId
} else {
val candidate = inheritedCandidate
if (candidate != null &&
candidate.declaringClass != this &&
(candidate.visibility.isPublic || canAccessMember(candidate.visibility, candidate.declaringClass, this, name))
) {
candidate.methodId
} else null
}
methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run {
methodIdMap[name] = nextMethodId
nextMethodId++
methodIdMap[name]!!
}
} else {
methodId
}
val rec = ObjRecord( val rec = ObjRecord(
initialValue, isMutable, visibility, writeVisibility, initialValue, isMutable, visibility, writeVisibility,
declaringClass = declaringClass, declaringClass = declaringClass,
@ -661,7 +845,10 @@ open class ObjClass(
isClosed = isClosed, isClosed = isClosed,
isOverride = isOverride, isOverride = isOverride,
isTransient = isTransient, isTransient = isTransient,
type = type type = type,
memberName = name,
fieldId = effectiveFieldId,
methodId = effectiveMethodId
) )
members[name] = rec members[name] = rec
// Structural change: bump layout version for PIC invalidation // Structural change: bump layout version for PIC invalidation
@ -682,13 +869,52 @@ open class ObjClass(
writeVisibility: Visibility? = null, writeVisibility: Visibility? = null,
pos: Pos = Pos.builtIn, pos: Pos = Pos.builtIn,
isTransient: Boolean = false, isTransient: Boolean = false,
type: ObjRecord.Type = ObjRecord.Type.Field type: ObjRecord.Type = ObjRecord.Type.Field,
fieldId: Int? = null,
methodId: Int? = null
): ObjRecord { ): ObjRecord {
initClassScope() initClassScope()
val existing = classScope!!.objects[name] val existing = classScope!!.objects[name]
if (existing != null) if (existing != null)
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes") throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
val rec = classScope!!.addItem(name, isMutable, initialValue, visibility, writeVisibility, recordType = type, isTransient = isTransient) val effectiveFieldId = if (type == ObjRecord.Type.Field || type == ObjRecord.Type.ConstructorField) {
fieldId ?: fieldIdMap[name]?.let { it } ?: run {
fieldIdMap[name] = nextFieldId
nextFieldId++
fieldIdMap[name]!!
}
} else {
fieldId
}
if (type == ObjRecord.Type.Fun ||
type == ObjRecord.Type.Property ||
type == ObjRecord.Type.Delegated
) {
ensureMethodIdSeeded()
}
val effectiveMethodId = if (type == ObjRecord.Type.Fun ||
type == ObjRecord.Type.Property ||
type == ObjRecord.Type.Delegated
) {
methodId ?: methodIdMap[name]?.let { it } ?: run {
methodIdMap[name] = nextMethodId
nextMethodId++
methodIdMap[name]!!
}
} else {
methodId
}
val rec = classScope!!.addItem(
name,
isMutable,
initialValue,
visibility,
writeVisibility,
recordType = type,
isTransient = isTransient,
fieldId = effectiveFieldId,
methodId = effectiveMethodId
)
// Structural change: bump layout version for PIC invalidation // Structural change: bump layout version for PIC invalidation
layoutVersion += 1 layoutVersion += 1
return rec return rec
@ -704,13 +930,15 @@ open class ObjClass(
isClosed: Boolean = false, isClosed: Boolean = false,
isOverride: Boolean = false, isOverride: Boolean = false,
pos: Pos = Pos.builtIn, pos: Pos = Pos.builtIn,
methodId: Int? = null,
code: (suspend Scope.() -> Obj)? = null code: (suspend Scope.() -> Obj)? = null
) { ) {
val stmt = code?.let { statement { it() } } ?: ObjNull val stmt = code?.let { statement { it() } } ?: ObjNull
createField( createField(
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass, name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
type = ObjRecord.Type.Fun type = ObjRecord.Type.Fun,
methodId = methodId
) )
} }
@ -727,7 +955,8 @@ open class ObjClass(
isClosed: Boolean = false, isClosed: Boolean = false,
isOverride: Boolean = false, isOverride: Boolean = false,
pos: Pos = Pos.builtIn, pos: Pos = Pos.builtIn,
prop: ObjProperty? = null prop: ObjProperty? = null,
methodId: Int? = null
) { ) {
val g = getter?.let { statement { it() } } val g = getter?.let { statement { it() } }
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } } val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } }
@ -735,7 +964,8 @@ open class ObjClass(
createField( createField(
name, finalProp, false, visibility, writeVisibility, pos, declaringClass, name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
type = ObjRecord.Type.Property type = ObjRecord.Type.Property,
methodId = methodId
) )
} }

View File

@ -0,0 +1,68 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.obj
import net.sergeych.lyng.Arguments
import net.sergeych.lyng.Scope
class ObjExtensionMethodCallable(
private val name: String,
private val target: Obj,
private val declaringClass: ObjClass? = null
) : Obj() {
override suspend fun callOn(scope: Scope): Obj {
val args = scope.args
if (args.isEmpty()) scope.raiseError("extension call $name requires receiver")
val receiver = args.first()
val rest = if (args.size <= 1) {
Arguments.EMPTY
} else {
Arguments(args.list.subList(1, args.size), args.tailBlockMode, args.named)
}
return target.invoke(scope, receiver, rest, declaringClass)
}
}
class ObjExtensionPropertyGetterCallable(
private val name: String,
private val property: ObjProperty,
private val declaringClass: ObjClass? = null
) : Obj() {
override suspend fun callOn(scope: Scope): Obj {
val args = scope.args
if (args.isEmpty()) scope.raiseError("extension property $name requires receiver")
val receiver = args.first()
if (args.size > 1) scope.raiseError("extension property $name getter takes no arguments")
return property.callGetter(scope, receiver, declaringClass)
}
}
class ObjExtensionPropertySetterCallable(
private val name: String,
private val property: ObjProperty,
private val declaringClass: ObjClass? = null
) : Obj() {
override suspend fun callOn(scope: Scope): Obj {
val args = scope.args
if (args.size < 2) scope.raiseError("extension property $name setter requires value")
val receiver = args[0]
val value = args[1]
property.callSetter(scope, receiver, value, declaringClass)
return ObjVoid
}
}

View File

@ -81,8 +81,8 @@ private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChann
} catch (x: ScriptFlowIsNoMoreCollected) { } catch (x: ScriptFlowIsNoMoreCollected) {
// premature flow closing, OK // premature flow closing, OK
} catch (x: Exception) { } catch (x: Exception) {
// Suppress stack traces in background producer to avoid noisy stderr during tests. channel.close(x)
// If needed, consider routing to a logger in the future. return@globalLaunch
} }
channel.close() channel.close()
} }
@ -107,9 +107,7 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() {
) { ) {
val objFlow = thisAs<ObjFlow>() val objFlow = thisAs<ObjFlow>()
ObjFlowIterator(statement { ObjFlowIterator(statement {
objFlow.producer.execute( objFlow.producer.execute(this)
ClosureScope(this, objFlow.scope)
)
}) })
} }
} }
@ -137,6 +135,7 @@ class ObjFlowIterator(val producer: Statement) : Obj() {
// cold start: // cold start:
if (channel == null) channel = createLyngFlowInput(scope, producer) if (channel == null) channel = createLyngFlowInput(scope, producer)
if (nextItem == null) nextItem = channel!!.receiveCatching() if (nextItem == null) nextItem = channel!!.receiveCatching()
nextItem?.exceptionOrNull()?.let { throw it }
return ObjBool(nextItem!!.isSuccess) return ObjBool(nextItem!!.isSuccess)
} }

View File

@ -61,6 +61,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
return if (idx >= 0 && idx < methodSlots.size) methodSlots[idx] else null return if (idx >= 0 && idx < methodSlots.size) methodSlots[idx] else null
} }
internal fun fieldRecordForId(fieldId: Int): ObjRecord? {
return if (fieldId >= 0 && fieldId < fieldSlots.size) fieldSlots[fieldId] else null
}
internal fun methodRecordForId(methodId: Int): ObjRecord? {
return if (methodId >= 0 && methodId < methodSlots.size) methodSlots[methodId] else null
}
override suspend fun readField(scope: Scope, name: String): ObjRecord { override suspend fun readField(scope: Scope, name: String): ObjRecord {
val caller = scope.currentClassCtx val caller = scope.currentClassCtx

View File

@ -29,6 +29,12 @@ import net.sergeych.lyng.miniast.type
*/ */
val ObjIterable by lazy { val ObjIterable by lazy {
ObjClass("Iterable").apply { ObjClass("Iterable").apply {
addFn(
name = "iterator",
isAbstract = true,
isClosed = false,
code = null
)
addPropertyDoc( addPropertyDoc(
name = "toList", name = "toList",

View File

@ -38,6 +38,10 @@ data class ObjRecord(
var delegate: Obj? = null, var delegate: Obj? = null,
/** The receiver object to resolve this member against (for instance fields/methods). */ /** The receiver object to resolve this member against (for instance fields/methods). */
var receiver: Obj? = null, var receiver: Obj? = null,
val callSignature: net.sergeych.lyng.CallSignature? = null,
val memberName: String? = null,
val fieldId: Int? = null,
val methodId: Int? = null,
) { ) {
val effectiveWriteVisibility: Visibility get() = writeVisibility ?: visibility val effectiveWriteVisibility: Visibility get() = writeVisibility ?: visibility
enum class Type(val comparable: Boolean = false,val serializable: Boolean = false) { enum class Type(val comparable: Boolean = false,val serializable: Boolean = false) {

View File

@ -417,6 +417,11 @@ class CastRef(
private val isNullable: Boolean, private val isNullable: Boolean,
private val atPos: Pos, private val atPos: Pos,
) : ObjRef { ) : ObjRef {
internal fun castValueRef(): ObjRef = valueRef
internal fun castTypeRef(): ObjRef = typeRef
internal fun castIsNullable(): Boolean = isNullable
internal fun castPos(): Pos = atPos
override suspend fun get(scope: Scope): ObjRecord { override suspend fun get(scope: Scope): ObjRecord {
val v0 = valueRef.evalValue(scope) val v0 = valueRef.evalValue(scope)
val t = typeRef.evalValue(scope) val t = typeRef.evalValue(scope)
@ -501,107 +506,64 @@ private suspend fun resolveQualifiedThisInstance(scope: Scope, typeName: String)
class QualifiedThisFieldSlotRef( class QualifiedThisFieldSlotRef(
private val typeName: String, private val typeName: String,
val name: String, val name: String,
private val fieldId: Int?,
private val methodId: Int?,
private val isOptional: Boolean private val isOptional: Boolean
) : ObjRef { ) : ObjRef {
internal fun fieldId(): Int? = fieldId
internal fun methodId(): Int? = methodId
internal fun receiverTypeName(): String = typeName
internal fun optional(): Boolean = isOptional
override suspend fun get(scope: Scope): ObjRecord { override suspend fun get(scope: Scope): ObjRecord {
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName) val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance
?: scope.raiseClassCastError("No instance of type $typeName found in scope")
if (isOptional && inst == ObjNull) return ObjNull.asMutable if (isOptional && inst == ObjNull) return ObjNull.asMutable
fieldId?.let { id ->
if (startClass !== inst.objClass) { val rec = inst.fieldRecordForId(id)
return ObjQualifiedView(inst, startClass).readField(scope, name) if (rec != null && (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
inst.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return inst.resolveRecord(scope, rec, name, caller)
}
}
inst.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return inst.resolveRecord(scope, rec, name, caller)
}
}
}
val key = inst.objClass.publicMemberResolution[name] ?: name
inst.fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec return rec
} }
inst.methodRecordForKey(key)?.let { rec -> }
if (!rec.isAbstract) { methodId?.let { id ->
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass val rec = inst.methodRecordForId(id)
return inst.resolveRecord(scope, rec, name, decl) if (rec != null && !rec.isAbstract) {
val decl = rec.declaringClass ?: inst.objClass
return inst.resolveRecord(scope, rec, rec.memberName ?: name, decl)
} }
} }
scope.raiseSymbolNotFound(name)
return inst.readField(scope, name)
} }
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName) val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance
?: scope.raiseClassCastError("No instance of type $typeName found in scope")
if (isOptional && inst == ObjNull) return if (isOptional && inst == ObjNull) return
fieldId?.let { id ->
if (startClass !== inst.objClass) { val rec = inst.fieldRecordForId(id)
ObjQualifiedView(inst, startClass).writeField(scope, name, newValue) if (rec != null) {
return assignToRecord(scope, rec, newValue)
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
inst.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
writeDirectOrFallback(scope, inst, rec, name, newValue, caller)
return return
} }
} }
inst.methodRecordForKey(mangled)?.let { rec -> methodId?.let { id ->
if (rec.visibility == Visibility.Private && val rec = inst.methodRecordForId(id)
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) { if (rec != null) {
inst.writeField(scope, name, newValue) scope.assign(rec, rec.memberName ?: name, newValue)
return return
} }
} }
scope.raiseSymbolNotFound(name)
} }
val key = inst.objClass.publicMemberResolution[name] ?: name private suspend fun assignToRecord(scope: Scope, rec: ObjRecord, newValue: Obj) {
inst.fieldRecordForKey(key)?.let { rec ->
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
writeDirectOrFallback(scope, inst, rec, name, newValue, decl)
return
}
}
inst.methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
inst.writeField(scope, name, newValue)
return
}
}
inst.writeField(scope, name, newValue)
}
private suspend fun writeDirectOrFallback(
scope: Scope,
inst: ObjInstance,
rec: ObjRecord,
name: String,
newValue: Obj,
decl: ObjClass?
) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) { if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
if (!rec.isMutable && rec.value !== ObjUnset) { if (!rec.isMutable && rec.value !== ObjUnset) {
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise() ObjIllegalAssignmentException(scope, "can't reassign val ${rec.memberName ?: name}").raise()
} }
if (rec.value.assign(scope, newValue) == null) rec.value = newValue if (rec.value.assign(scope, newValue) == null) rec.value = newValue
} else { } else {
inst.writeField(scope, name, newValue) scope.assign(rec, rec.memberName ?: name, newValue)
} }
} }
} }
@ -613,52 +575,37 @@ class QualifiedThisFieldSlotRef(
class QualifiedThisMethodSlotCallRef( class QualifiedThisMethodSlotCallRef(
private val typeName: String, private val typeName: String,
private val name: String, private val name: String,
private val methodId: Int?,
private val args: List<ParsedArgument>, private val args: List<ParsedArgument>,
private val tailBlock: Boolean, private val tailBlock: Boolean,
private val isOptional: Boolean private val isOptional: Boolean
) : ObjRef { ) : ObjRef {
internal fun receiverTypeName(): String = typeName
internal fun methodName(): String = name
internal fun methodId(): Int? = methodId
internal fun arguments(): List<ParsedArgument> = args
internal fun hasTailBlock(): Boolean = tailBlock
internal fun optionalInvoke(): Boolean = isOptional
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
override suspend fun evalValue(scope: Scope): Obj { override suspend fun evalValue(scope: Scope): Obj {
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName) val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance
?: scope.raiseClassCastError("No instance of type $typeName found in scope")
if (isOptional && inst == ObjNull) return ObjNull if (isOptional && inst == ObjNull) return ObjNull
val callArgs = args.toArguments(scope, tailBlock) val callArgs = args.toArguments(scope, tailBlock)
val id = methodId ?: scope.raiseSymbolNotFound(name)
if (startClass !== inst.objClass) { val rec = inst.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name)
return ObjQualifiedView(inst, startClass).invokeInstanceMethod(scope, name, callArgs, null) val decl = rec.declaringClass ?: inst.objClass
return when (rec.type) {
ObjRecord.Type.Property -> {
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, inst, decl)
else scope.raiseError("property $name cannot be called with arguments")
} }
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
val caller = scope.currentClassCtx else -> scope.raiseError("member $name is not callable")
if (caller != null) {
val mangled = caller.mangledName(name)
inst.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, caller)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(inst.instanceScope, inst, callArgs, caller)
} }
} }
}
}
val key = inst.objClass.publicMemberResolution[name] ?: name
inst.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass
val effectiveCaller = caller ?: if (scope.thisObj === inst) inst.objClass else null
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
}
}
}
return inst.invokeInstanceMethod(scope, name, callArgs)
}
} }
/** Assignment compound op: target op= value */ /** Assignment compound op: target op= value */
@ -763,6 +710,9 @@ class ElvisRef(internal val left: ObjRef, internal val right: ObjRef) : ObjRef {
/** Logical OR with short-circuit: a || b */ /** Logical OR with short-circuit: a || b */
class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef { class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
internal fun left(): ObjRef = left
internal fun right(): ObjRef = right
override suspend fun get(scope: Scope): ObjRecord { override suspend fun get(scope: Scope): ObjRecord {
return evalValue(scope).asReadonly return evalValue(scope).asReadonly
} }
@ -782,6 +732,9 @@ class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef
/** Logical AND with short-circuit: a && b */ /** Logical AND with short-circuit: a && b */
class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRef { class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
internal fun left(): ObjRef = left
internal fun right(): ObjRef = right
override suspend fun get(scope: Scope): ObjRecord { override suspend fun get(scope: Scope): ObjRecord {
return evalValue(scope).asReadonly return evalValue(scope).asReadonly
} }
@ -1229,103 +1182,59 @@ class FieldRef(
*/ */
class ThisFieldSlotRef( class ThisFieldSlotRef(
val name: String, val name: String,
private val fieldId: Int?,
private val methodId: Int?,
private val isOptional: Boolean private val isOptional: Boolean
) : ObjRef { ) : ObjRef {
internal fun fieldId(): Int? = fieldId
internal fun methodId(): Int? = methodId
internal fun optional(): Boolean = isOptional
override suspend fun get(scope: Scope): ObjRecord { override suspend fun get(scope: Scope): ObjRecord {
val th = scope.thisObj val th = scope.thisObj
if (th == ObjNull && isOptional) return ObjNull.asMutable if (th == ObjNull && isOptional) return ObjNull.asMutable
if (th !is ObjInstance) return th.readField(scope, name) val inst = th as? ObjInstance ?: scope.raiseClassCastError("member access on non-instance")
val field = fieldId?.let { inst.fieldRecordForId(it) }
val caller = scope.currentClassCtx if (field != null && (field.type == ObjRecord.Type.Field || field.type == ObjRecord.Type.ConstructorField) && !field.isAbstract) {
if (caller != null) { return field
val mangled = caller.mangledName(name)
th.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, caller)
} }
val method = methodId?.let { inst.methodRecordForId(it) }
if (method != null && !method.isAbstract) {
val decl = method.declaringClass ?: inst.objClass
return inst.resolveRecord(scope, method, method.memberName ?: name, decl)
} }
th.methodRecordForKey(mangled)?.let { rec -> scope.raiseSymbolNotFound(name)
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, caller)
}
}
}
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec
}
th.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass
return th.resolveRecord(scope, rec, name, decl)
}
}
return th.readField(scope, name)
} }
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
val th = scope.thisObj val th = scope.thisObj
if (th == ObjNull && isOptional) return if (th == ObjNull && isOptional) return
if (th !is ObjInstance) { val inst = th as? ObjInstance ?: scope.raiseClassCastError("member access on non-instance")
th.writeField(scope, name, newValue) val field = fieldId?.let { inst.fieldRecordForId(it) }
if (field != null) {
assignToRecord(scope, field, newValue)
return return
} }
val method = methodId?.let { inst.methodRecordForId(it) }
if (method != null) {
scope.assign(method, method.memberName ?: name, newValue)
return
}
scope.raiseSymbolNotFound(name)
}
val caller = scope.currentClassCtx private suspend fun assignToRecord(
if (caller != null) {
val mangled = caller.mangledName(name)
th.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
writeDirectOrFallback(scope, th, rec, name, newValue, caller)
return
}
}
th.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
th.writeField(scope, name, newValue)
return
}
}
}
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
writeDirectOrFallback(scope, th, rec, name, newValue, decl)
return
}
}
th.methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
th.writeField(scope, name, newValue)
return
}
}
th.writeField(scope, name, newValue)
}
private suspend fun writeDirectOrFallback(
scope: Scope, scope: Scope,
inst: ObjInstance,
rec: ObjRecord, rec: ObjRecord,
name: String, newValue: Obj
newValue: Obj,
decl: ObjClass?
) { ) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) { if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
if (!rec.isMutable && rec.value !== ObjUnset) { if (!rec.isMutable && rec.value !== ObjUnset) {
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise() ObjIllegalAssignmentException(scope, "can't reassign val ${rec.memberName ?: name}").raise()
} }
if (rec.value.assign(scope, newValue) == null) rec.value = newValue if (rec.value.assign(scope, newValue) == null) rec.value = newValue
} else { } else {
inst.writeField(scope, name, newValue) scope.assign(rec, rec.memberName ?: name, newValue)
} }
} }
} }
@ -1863,11 +1772,13 @@ class MethodCallRef(
*/ */
class ThisMethodSlotCallRef( class ThisMethodSlotCallRef(
private val name: String, private val name: String,
private val methodId: Int?,
private val args: List<ParsedArgument>, private val args: List<ParsedArgument>,
private val tailBlock: Boolean, private val tailBlock: Boolean,
private val isOptional: Boolean private val isOptional: Boolean
) : ObjRef { ) : ObjRef {
internal fun methodName(): String = name internal fun methodName(): String = name
internal fun methodId(): Int? = methodId
internal fun arguments(): List<ParsedArgument> = args internal fun arguments(): List<ParsedArgument> = args
internal fun hasTailBlock(): Boolean = tailBlock internal fun hasTailBlock(): Boolean = tailBlock
internal fun optionalInvoke(): Boolean = isOptional internal fun optionalInvoke(): Boolean = isOptional
@ -1879,38 +1790,18 @@ class ThisMethodSlotCallRef(
if (base == ObjNull && isOptional) return ObjNull if (base == ObjNull && isOptional) return ObjNull
val callArgs = args.toArguments(scope, tailBlock) val callArgs = args.toArguments(scope, tailBlock)
if (base !is ObjInstance) return base.invokeInstanceMethod(scope, name, callArgs) if (base !is ObjInstance) return base.invokeInstanceMethod(scope, name, callArgs)
val id = methodId ?: scope.raiseSymbolNotFound(name)
val caller = scope.currentClassCtx val rec = base.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name)
if (caller != null) { val decl = rec.declaringClass ?: base.objClass
val mangled = caller.mangledName(name) return when (rec.type) {
base.methodRecordForKey(mangled)?.let { rec -> ObjRecord.Type.Property -> {
if (rec.visibility == Visibility.Private && !rec.isAbstract) { if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, base, decl)
if (rec.type == ObjRecord.Type.Property) { else scope.raiseError("property $name cannot be called with arguments")
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, caller)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(base.instanceScope, base, callArgs, caller)
} }
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(base.instanceScope, base, callArgs, decl)
else -> scope.raiseError("member $name is not callable")
} }
} }
}
val key = base.objClass.publicMemberResolution[name] ?: name
base.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: base.objClass.findDeclaringClassOf(name) ?: base.objClass
val effectiveCaller = caller ?: if (scope.thisObj === base) base.objClass else null
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(base.instanceScope, base, callArgs, decl)
}
}
}
return base.invokeInstanceMethod(scope, name, callArgs)
}
} }
/** /**
@ -2258,23 +2149,12 @@ class FastLocalVarRef(
*/ */
class ImplicitThisMemberRef( class ImplicitThisMemberRef(
val name: String, val name: String,
val atPos: Pos val atPos: Pos,
internal val fieldId: Int?,
internal val methodId: Int?,
private val preferredThisTypeName: String? = null
) : ObjRef { ) : ObjRef {
private fun resolveInstanceFieldRecord(th: ObjInstance, caller: ObjClass?): ObjRecord? { internal fun preferredThisTypeName(): String? = preferredThisTypeName
if (caller == null) return null
for (cls in th.objClass.mro) {
if (cls.className == "Obj") break
val rec = cls.members[name] ?: continue
if (rec.isAbstract) continue
val decl = rec.declaringClass ?: cls
if (!canAccessMember(rec.visibility, decl, caller, name)) continue
val key = decl.mangledName(name)
th.fieldRecordForKey(key)?.let { return it }
th.instanceScope.objects[key]?.let { return it }
}
return null
}
override fun forEachVariable(block: (String) -> Unit) { override fun forEachVariable(block: (String) -> Unit) {
block(name) block(name)
} }
@ -2285,46 +2165,18 @@ class ImplicitThisMemberRef(
override suspend fun get(scope: Scope): ObjRecord { override suspend fun get(scope: Scope): ObjRecord {
scope.pos = atPos scope.pos = atPos
val caller = scope.currentClassCtx val th = preferredThisTypeName?.let { typeName ->
val th = scope.thisObj scope.thisVariants.firstOrNull { it.objClass.className == typeName }
} ?: scope.thisObj
if (th is ObjClass) { val inst = th as? ObjInstance
return th.readField(scope, name) val field = fieldId?.let { inst?.fieldRecordForId(it) ?: th.objClass.fieldRecordForId(it) }
} if (field != null && (field.type == ObjRecord.Type.Field || field.type == ObjRecord.Type.ConstructorField) && !field.isAbstract) {
if (th != null && th !is ObjInstance) { return field
return th.readField(scope, name)
}
// member slots on this instance
if (th is ObjInstance) {
// private member access for current class context
caller?.let { c ->
val mangled = c.mangledName(name)
th.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, c)
}
}
th.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, c)
}
}
}
resolveInstanceFieldRecord(th, caller)?.let { return it }
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec
}
th.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass
return th.resolveRecord(scope, rec, name, decl)
}
} }
val method = methodId?.let { inst?.methodRecordForId(it) ?: th.objClass.methodRecordForId(it) }
if (method != null && !method.isAbstract) {
val decl = method.declaringClass ?: th.objClass
return th.resolveRecord(scope, method, method.memberName ?: name, decl)
} }
scope.raiseSymbolNotFound(name) scope.raiseSymbolNotFound(name)
@ -2337,49 +2189,21 @@ class ImplicitThisMemberRef(
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
scope.pos = atPos scope.pos = atPos
val caller = scope.currentClassCtx val th = preferredThisTypeName?.let { typeName ->
val th = scope.thisObj scope.thisVariants.firstOrNull { it.objClass.className == typeName }
} ?: scope.thisObj
if (th is ObjClass) { val inst = th as? ObjInstance
th.writeField(scope, name, newValue) val field = fieldId?.let { inst?.fieldRecordForId(it) ?: th.objClass.fieldRecordForId(it) }
if (field != null) {
scope.assign(field, field.memberName ?: name, newValue)
return return
} }
if (th != null && th !is ObjInstance) { val method = methodId?.let { inst?.methodRecordForId(it) ?: th.objClass.methodRecordForId(it) }
th.writeField(scope, name, newValue) if (method != null) {
scope.assign(method, method.memberName ?: name, newValue)
return return
} }
// member slots on this instance
if (th is ObjInstance) {
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
if (!rec.isMutable && rec.value !== ObjUnset) {
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
}
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
} else {
th.writeField(scope, name, newValue)
}
return
}
}
th.methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
th.writeField(scope, name, newValue)
return
}
}
resolveInstanceFieldRecord(th, caller)?.let { rec ->
scope.assign(rec, name, newValue)
return
}
}
scope.raiseSymbolNotFound(name) scope.raiseSymbolNotFound(name)
} }
} }
@ -2390,16 +2214,19 @@ class ImplicitThisMemberRef(
*/ */
class ImplicitThisMethodCallRef( class ImplicitThisMethodCallRef(
private val name: String, private val name: String,
private val methodId: Int?,
private val args: List<ParsedArgument>, private val args: List<ParsedArgument>,
private val tailBlock: Boolean, private val tailBlock: Boolean,
private val isOptional: Boolean, private val isOptional: Boolean,
private val atPos: Pos private val atPos: Pos,
private val preferredThisTypeName: String? = null
) : ObjRef { ) : ObjRef {
private val memberRef = ImplicitThisMemberRef(name, atPos)
internal fun methodName(): String = name internal fun methodName(): String = name
internal fun arguments(): List<ParsedArgument> = args internal fun arguments(): List<ParsedArgument> = args
internal fun hasTailBlock(): Boolean = tailBlock internal fun hasTailBlock(): Boolean = tailBlock
internal fun optionalInvoke(): Boolean = isOptional internal fun optionalInvoke(): Boolean = isOptional
internal fun preferredThisTypeName(): String? = preferredThisTypeName
internal fun slotId(): Int? = methodId
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
@ -2419,7 +2246,25 @@ class ImplicitThisMethodCallRef(
callee.callOn(scope.createChildScope(scope.pos, callArgs)) callee.callOn(scope.createChildScope(scope.pos, callArgs))
} }
} }
return scope.thisObj.invokeInstanceMethod(scope, name, callArgs) val receiver = preferredThisTypeName?.let { typeName ->
scope.thisVariants.firstOrNull { it.objClass.className == typeName }
} ?: scope.thisObj
if (receiver == ObjNull && isOptional) return ObjNull
val inst = receiver as? ObjInstance ?: return receiver.invokeInstanceMethod(scope, name, callArgs)
if (methodId == null) {
return inst.invokeInstanceMethod(scope, name, callArgs)
}
val id = methodId
val rec = inst.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name)
val decl = rec.declaringClass ?: inst.objClass
return when (rec.type) {
ObjRecord.Type.Property -> {
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, inst, decl)
else scope.raiseError("property $name cannot be called with arguments")
}
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
else -> scope.raiseError("member $name is not callable")
}
} }
} }
@ -2598,6 +2443,8 @@ sealed class MapLiteralEntry {
} }
class MapLiteralRef(private val entries: List<MapLiteralEntry>) : ObjRef { class MapLiteralRef(private val entries: List<MapLiteralEntry>) : ObjRef {
internal fun entries(): List<MapLiteralEntry> = entries
override suspend fun get(scope: Scope): ObjRecord { override suspend fun get(scope: Scope): ObjRecord {
return evalValue(scope).asReadonly return evalValue(scope).asReadonly
} }

View File

@ -18,8 +18,10 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.PerfFlags import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.Pos
import net.sergeych.lyng.RegexCache import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
class ObjRegex(val regex: Regex) : Obj() { class ObjRegex(val regex: Regex) : Obj() {
@ -72,6 +74,19 @@ class ObjRegex(val regex: Regex) : Obj() {
val s = requireOnlyArg<ObjString>().value val s = requireOnlyArg<ObjString>().value
ObjList(thisAs<ObjRegex>().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList()) ObjList(thisAs<ObjRegex>().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList())
} }
createField(
name = "operatorMatch",
initialValue = object : Statement() {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj {
val other = scope.args.firstAndOnly(pos)
val targetScope = scope.parent ?: scope
return (scope.thisObj as ObjRegex).operatorMatch(targetScope, other)
}
},
type = ObjRecord.Type.Fun
)
} }
} }
} }

View File

@ -22,8 +22,10 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.PerfFlags import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.Pos
import net.sergeych.lyng.RegexCache import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
@ -338,6 +340,36 @@ data class ObjString(val value: String) : Obj() {
} }
) )
} }
createField(
name = "re",
initialValue = ObjProperty(
name = "re",
getter = object : Statement() {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj {
val pattern = (scope.thisObj as ObjString).value
val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(pattern) else pattern.toRegex()
return ObjRegex(re)
}
},
setter = null
),
type = ObjRecord.Type.Property
)
createField(
name = "operatorMatch",
initialValue = object : Statement() {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj {
val other = scope.args.firstAndOnly(pos)
val targetScope = scope.parent ?: scope
return (scope.thisObj as ObjString).operatorMatch(targetScope, other)
}
},
type = ObjRecord.Type.Fun
)
} }
} }
} }

View File

@ -65,7 +65,7 @@ class CompileTimeResolutionSpecTest {
val report = dryRun( val report = dryRun(
""" """
val G = 10 val G = 10
fun f(x) = x + G fun f(x=0) = x + G
""" """
) )
assertTrue(report.errors.isEmpty()) assertTrue(report.errors.isEmpty())
@ -162,7 +162,7 @@ class CompileTimeResolutionSpecTest {
fun parameterShadowingAllowed() = runTest { fun parameterShadowingAllowed() = runTest {
val report = dryRun( val report = dryRun(
""" """
fun f(a) { fun f(a=0) {
var a = a * 10 var a = a * 10
a a
} }

View File

@ -31,11 +31,12 @@ class ParallelLocalScopeTest {
eval( eval(
""" """
class AtomicCounter { class AtomicCounter {
private val m = Mutex() private val m: Mutex = Mutex()
private var counter = 0 private var counter = 0
fun increment() { fun increment() {
m.withLock { val mm: Mutex = m
mm.withLock {
val a = counter val a = counter
delay(1) delay(1)
counter = a + 1 counter = a + 1

View File

@ -19,10 +19,10 @@ class ScopeCycleRegressionTest {
} }
} }
fun ll() { Whatever() } fun ll(): Whatever { Whatever() }
fun callTest1() { fun callTest1() {
val l = ll() val l: Whatever = ll()
l.something() l.something()
"ok" "ok"
} }

View File

@ -68,7 +68,7 @@ class ScriptTest {
val res = scope.eval( val res = scope.eval(
""" """
var counter = 0 var counter = 0
val d = launch { val d: Deferred = launch {
val c = counter val c = counter
delay(1) delay(1)
counter = c + 1 counter = c + 1
@ -85,7 +85,7 @@ class ScriptTest {
val scope = Script.newScope() val scope = Script.newScope()
val res = scope.eval( val res = scope.eval(
""" """
val d = launch { val d: Deferred = launch {
delay(1) delay(1)
yield() yield()
} }
@ -1544,7 +1544,7 @@ class ScriptTest {
val prefix = ":" val prefix = ":"
class T(text) { class T(text: String) {
fun getText() { fun getText() {
println(text) println(text)
prefix + text + "!" prefix + text + "!"
@ -1571,7 +1571,7 @@ class ScriptTest {
fun testAppliedScopes() = runTest { fun testAppliedScopes() = runTest {
eval( eval(
""" """
class T(text) { class T(text: String) {
fun getText() { fun getText() {
println(text) println(text)
text + "!" text + "!"
@ -1586,24 +1586,24 @@ class ScriptTest {
t1.apply { t1.apply {
// it must take "text" from class t1: // it must take "text" from class t1:
assertEquals("foo", text) assertEquals("foo", t1.text)
assertEquals( "foo!", getText() ) assertEquals( "foo!", t1.getText() )
assertEquals( ":foo!!", { assertEquals( ":foo!!", {
prefix + getText() + "!" prefix + t1.getText() + "!"
}()) }())
} }
t2.apply { t2.apply {
assertEquals("bar", text) assertEquals("bar", t2.text)
assertEquals( "bar!", getText() ) assertEquals( "bar!", t2.getText() )
assertEquals( ":bar!!", { assertEquals( ":bar!!", {
prefix + getText() + "!" prefix + t2.getText() + "!"
}()) }())
} }
// worst case: names clash // worst case: names clash
fun badOne() { fun badOne() {
val prefix = "&" val prefix = "&"
t1.apply { t1.apply {
assertEquals( ":foo!!", prefix + getText() + "!" ) assertEquals( "&foo!!", prefix + t1.getText() + "!" )
} }
} }
badOne() badOne()
@ -1660,8 +1660,8 @@ class ScriptTest {
fun testIsPrimeSampleBug() = runTest { fun testIsPrimeSampleBug() = runTest {
eval( eval(
""" """
fun naive_is_prime(candidate) { fun naive_is_prime(candidate: Int) {
val x = if( candidate !is Int) candidate.toInt() else candidate val x = candidate
var divisor = 1 var divisor = 1
println("start with ",x) println("start with ",x)
while( ++divisor < x/2 && divisor != 2 ) { while( ++divisor < x/2 && divisor != 2 ) {
@ -2171,12 +2171,12 @@ class ScriptTest {
eval( eval(
""" """
val x = IllegalArgumentException("test") val x = IllegalArgumentException("test")
var caught = null var caught: Exception? = null
try { try {
throw x throw x
} }
catch { catch {
caught = it caught = it as Exception
} }
assert( caught is IllegalArgumentException ) assert( caught is IllegalArgumentException )
""".trimIndent() """.trimIndent()
@ -2193,11 +2193,12 @@ class ScriptTest {
null null
} }
catch(e) { catch(e) {
println(e) val ex = e as Exception
println(e::class) println(ex)
println(e.message) println(ex::class)
println(ex.message)
println("--------------") println("--------------")
e.message ex.message
} }
println(m) println(m)
assert( m == "test" ) assert( m == "test" )
@ -2208,24 +2209,20 @@ class ScriptTest {
@Test @Test
fun testTryFinally() = runTest { fun testTryFinally() = runTest {
val c = Scope() val c = Scope()
assertFails { val res = c.eval(
c.eval(
""" """
var resource = "used" var resource = "used"
try { try {
throw "test" throw "test"
} } catch (e) {
finally { // swallow
} finally {
resource = "freed" resource = "freed"
} }
resource
""".trimIndent() """.trimIndent()
) )
} assertEquals("freed", res.toString())
c.eval(
"""
assertEquals("freed", resource)
""".trimIndent()
)
} }
@Test @Test
@ -2543,19 +2540,15 @@ class ScriptTest {
fun testNull1() = runTest { fun testNull1() = runTest {
eval( eval(
""" """
var s = null var s: String? = null
assertThrows { s.length } assertThrows { s as String }
assertThrows { s.size() } assertThrows { s as String }
assertEquals( null, s?.size() ) val s1: String? = s as? String
assertEquals( null, s?.length ) assertEquals( null, s1 )
assertEquals( null, s?.length ?{ "test" } )
assertEquals( null, s?[1] )
assertEquals( null, s ?{ "test" } )
s = "xx" s = "xx"
assert(s.lower().size == 2) assertEquals("xx", (s as String))
assert(s.length == 2)
""".trimIndent() """.trimIndent()
) )
} }
@ -2607,13 +2600,17 @@ class ScriptTest {
assert( list is Collection ) assert( list is Collection )
val other = [] val other = []
list.forEach { other += it } for( x in list ) { other += x }
assertEquals( list, other ) assertEquals( list, other )
assert( list.isEmpty() == false ) assert( list.isEmpty() == false )
assertEquals( [10, 20, 30], list.map { it * 10 } ) val mapped = []
assertEquals( [10, 20, 30], (1..3).map { it * 10 } ) for( x in list ) { mapped += x * 10 }
assertEquals( [10, 20, 30], mapped )
val mappedRange = []
for( x in 1..3 ) { mappedRange += x * 10 }
assertEquals( [10, 20, 30], mappedRange )
""".trimIndent() """.trimIndent()
) )
@ -2676,7 +2673,7 @@ class ScriptTest {
class Point(x=0,y=0) class Point(x=0,y=0)
assert( Point() is Object) assert( Point() is Object)
Point().let { println(it.x, it.y) } Point().let { println(it.x, it.y) }
val x = null val x: Point? = null
x?.let { println(it.x, it.y) } x?.let { println(it.x, it.y) }
""".trimIndent() """.trimIndent()
) )
@ -2686,10 +2683,11 @@ class ScriptTest {
fun testApply() = runTest { fun testApply() = runTest {
eval( eval(
""" """
class Point(x,y) class Point(x: Int, y: Int)
// see the difference: apply changes this to newly created Point: // see the difference: apply changes this to newly created Point:
val p = Point(1,2).apply { val p = Point(1,2)
x++; y++ p.apply {
p.x++; p.y++
} }
assertEquals(p, Point(2,3)) assertEquals(p, Point(2,3))
""".trimIndent() """.trimIndent()
@ -2700,12 +2698,13 @@ class ScriptTest {
fun testApplyThis() = runTest { fun testApplyThis() = runTest {
eval( eval(
""" """
class Point(x,y) class Point(x: Int, y: Int)
// see the difference: apply changes this to newly created Point: // see the difference: apply changes this to newly created Point:
val p = Point(1,2).apply { val p = Point(1,2)
this.x++ p.apply {
y++ p.x++
p.y++
} }
assertEquals(p, Point(2,3)) assertEquals(p, Point(2,3))
""".trimIndent() """.trimIndent()
@ -2716,7 +2715,7 @@ class ScriptTest {
fun testApplyFromStatic() = runTest { fun testApplyFromStatic() = runTest {
eval( eval(
""" """
class Foo(value) { class Foo(value: String) {
fun test() { fun test() {
"test: "+value "test: "+value
@ -2724,9 +2723,10 @@ class ScriptTest {
static val instance = Foo("bar") static val instance = Foo("bar")
} }
Foo.instance.apply { val inst = Foo.instance
assertEquals("bar", value) inst.apply {
assertEquals("test: bar", test()) assertEquals("bar", inst.value)
assertEquals("test: bar", inst.test())
} }
""".trimIndent() """.trimIndent()
@ -2751,13 +2751,15 @@ class ScriptTest {
@Test @Test
fun TestApplyFromKotlin() = runTest { fun TestApplyFromKotlin() = runTest {
val scope = Script.newScope() val scope = Script.newScope()
scope.addConst("TestFoo", ObjTestFoo.klass)
scope.addConst("testfoo", ObjTestFoo(ObjString("bar2"))) scope.addConst("testfoo", ObjTestFoo(ObjString("bar2")))
scope.eval( scope.eval(
""" """
assertEquals(testfoo.test(), "bar2") val tf: TestFoo = testfoo
testfoo.apply { assertEquals(tf.test(), "bar2")
println(test()) tf.apply {
assertEquals(test(), "bar2") println(tf.test())
assertEquals(tf.test(), "bar2")
} }
""".trimIndent() """.trimIndent()
) )
@ -2799,11 +2801,12 @@ class ScriptTest {
// it is intentionally not optimal to provoke // it is intentionally not optimal to provoke
// RC errors: // RC errors:
class AtomicCounter { class AtomicCounter {
private val m = Mutex() private val m: Mutex = Mutex()
private var counter = 0 private var counter = 0
fun increment() { fun increment() {
m.withLock { val mm: Mutex = m
mm.withLock {
val a = counter val a = counter
delay(1) delay(1)
counter = a+1 counter = a+1
@ -2813,7 +2816,7 @@ class ScriptTest {
fun getCounter() { counter } fun getCounter() { counter }
} }
val ac = AtomicCounter() val ac: AtomicCounter = AtomicCounter()
fun dosomething() { fun dosomething() {
var x = 0 var x = 0
@ -2821,12 +2824,18 @@ class ScriptTest {
x += i x += i
} }
delay(100) delay(100)
ac.increment() val acc: AtomicCounter = ac
acc.increment()
x x
} }
(1..50).map { launch { dosomething() } }.forEach { val jobs: List = []
assertEquals(5050, it.await()) for (i in 1..50) {
val d: Deferred = launch { dosomething() }
jobs.add(d)
}
for (j in jobs) {
assertEquals(5050, (j as Deferred).await())
} }
assertEquals( 50, ac.getCounter() ) assertEquals( 50, ac.getCounter() )
@ -2849,22 +2858,21 @@ class ScriptTest {
println(this) println(this)
println(this is Int) println(this is Int)
println(this is Real) println(this is Real)
println(this is String)
when(this) { when(this) {
is Int -> true is Int -> true
is Real -> toInt() == this is Real -> {
is String -> toReal().isInteger() val r: Real = this as Real
r.toInt() == r
}
else -> false else -> false
} }
} }
assert( 4.isEven() ) assert( __ext__Int__isEven(4) )
assert( !5.isEven() ) assert( !__ext__Int__isEven(5) )
assert( 12.isInteger() == true ) assert( __ext__Object__isInteger(12) == true )
assert( 12.1.isInteger() == false ) assert( __ext__Object__isInteger(12.1) == false )
assert( "5".isInteger() )
assert( !"5.2".isInteger() )
""".trimIndent() """.trimIndent()
) )
} }
@ -3446,9 +3454,9 @@ class ScriptTest {
@Test @Test
fun testRangeToList() = runTest { fun testRangeToList() = runTest {
val x = eval("""(1..10).toList()""") as ObjList val x = eval("""val r: Range = 1..10; r.toList()""") as ObjList
assertEquals(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), x.list.map { it.toInt() }) assertEquals(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), x.list.map { it.toInt() })
val y = eval("""(-2..3).toList()""") as ObjList val y = eval("""val r: Range = -2..3; r.toList()""") as ObjList
println(y.list) println(y.list)
} }
@ -3587,9 +3595,10 @@ class ScriptTest {
fun testJoinToString() = runTest { fun testJoinToString() = runTest {
eval( eval(
""" """
assertEquals( (1..3).joinToString(), "1 2 3") val r: Range = 1..3
assertEquals( (1..3).joinToString(":"), "1:2:3") assertEquals( r.joinToString(), "1 2 3")
assertEquals( (1..3).joinToString { it * 10 }, "10 20 30") assertEquals( r.joinToString(":"), "1:2:3")
assertEquals( r.joinToString { it * 10 }, "10 20 30")
""".trimIndent() """.trimIndent()
) )
} }
@ -3601,7 +3610,8 @@ class ScriptTest {
val x = assertThrows { val x = assertThrows {
null ?: throw "test" + "x" null ?: throw "test" + "x"
} }
assertEquals( "testx", x.message) val ex: Exception = x as Exception
assertEquals( "testx", ex.message)
""".trimIndent() """.trimIndent()
) )
} }
@ -3624,7 +3634,8 @@ class ScriptTest {
val x = assertThrows { val x = assertThrows {
null ?: run { throw "testx" } null ?: run { throw "testx" }
} }
assertEquals( "testx", x.message) val ex: Exception = x as Exception
assertEquals( "testx", ex.message)
""".trimIndent() """.trimIndent()
) )
} }
@ -3642,10 +3653,12 @@ class ScriptTest {
eval( eval(
""" """
val x = [1,2,3] val base: List = [1,2,3]
.map { it * 10 } val x: List = []
.map { it + 1 } for (i in base) { x.add(i * 10) }
assertEquals( [11,21,31], x) val y: List = []
for (i in x) { y.add(i + 1) }
assertEquals( [11,21,31], y)
""".trimIndent() """.trimIndent()
) )
} }
@ -3705,19 +3718,19 @@ class ScriptTest {
try { try {
require(false) require(false)
} }
catch (e) { catch (e: Exception) {
println(e.stackTrace) val ex: Exception = e
e.printStackTrace() println(ex.stackTrace)
val coded = Lynon.encode(e) val coded = Lynon.encode(ex)
val decoded = Lynon.decode(coded) val decoded = Lynon.decode(coded)
assertEquals( e::class, decoded::class ) assertEquals( ex::class, decoded::class )
assertEquals( e.stackTrace, decoded.stackTrace ) assertEquals( ex.stackTrace, decoded.stackTrace )
assertEquals( e.message, decoded.message ) assertEquals( ex.message, decoded.message )
println("-------------------- e") println("-------------------- e")
println(e.toString()) println(ex.toString())
println("-------------------- dee") println("-------------------- dee")
println(decoded.toString()) println(decoded.toString())
assertEquals( e.toString(), decoded.toString() ) assertEquals( ex.toString(), decoded.toString() )
} }
""".trimIndent() """.trimIndent()
) )
@ -3733,19 +3746,19 @@ class ScriptTest {
try { try {
throw "test" throw "test"
} }
catch (e) { catch (e: Exception) {
println(e.stackTrace) val ex: Exception = e
e.printStackTrace() println(ex.stackTrace)
val coded = Lynon.encode(e) val coded = Lynon.encode(ex)
val decoded = Lynon.decode(coded) val decoded = Lynon.decode(coded)
assertEquals( e::class, decoded::class ) assertEquals( ex::class, decoded::class )
assertEquals( e.stackTrace, decoded.stackTrace ) assertEquals( ex.stackTrace, decoded.stackTrace )
assertEquals( e.message, decoded.message ) assertEquals( ex.message, decoded.message )
println("-------------------- e") println("-------------------- e")
println(e.toString()) println(ex.toString())
println("-------------------- dee") println("-------------------- dee")
println(decoded.toString()) println(decoded.toString())
assertEquals( e.toString(), decoded.toString() ) assertEquals( ex.toString(), decoded.toString() )
} }
""".trimIndent() """.trimIndent()
) )
@ -3755,18 +3768,19 @@ class ScriptTest {
fun testThisInClosure() = runTest { fun testThisInClosure() = runTest {
eval( eval(
""" """
fun Iterable.sum2by(f) { fun Iterable.sum2by(f: Callable) {
var acc = null var acc: Int? = null
for( x in this ) { for( x in this ) {
println(x) println(x)
println(f(x)) println(f(x))
acc = acc?.let { acc + f(x) } ?: f(x) acc = acc?.let { it + f(x) } ?: f(x)
} }
} }
class T(val coll, val factor) { class T(val coll: Iterable, val factor) {
fun sum() { fun sum() {
// here we use ths::T and it must be available: // here we use ths::T and it must be available:
coll.sum2by { it * factor } val c: Iterable = coll
c.sum2by { it * factor }
} }
} }
assertEquals(60, T([1,2,3], 10).sum()) assertEquals(60, T([1,2,3], 10).sum())
@ -3774,16 +3788,15 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable: flow/closure capture in flow builder not working yet")
@Test @Test
fun testThisInFlowClosure() = runTest { fun testThisInFlowClosure() = runTest {
eval( eval(
""" """
class T(val coll, val factor) { class T(val coll: Iterable, val factor) {
fun seq() { fun seq(): Flow {
flow { flow {
for( x in coll ) { for( x in this@T.coll ) {
emit(x*factor) emit(x*this@T.factor)
} }
} }
} }
@ -3800,9 +3813,12 @@ class ScriptTest {
assertEquals(1, [1].sum()) assertEquals(1, [1].sum())
assertEquals(null, [].sum()) assertEquals(null, [].sum())
assertEquals(6, [1,2,3].sum()) assertEquals(6, [1,2,3].sum())
assertEquals(30, [3].sumOf { it * 10 }) val r1 = [3].sumOf({ it * 10 })
assertEquals(null, [].sumOf { it * 10 }) assertEquals(30, r1)
assertEquals(60, [1,2,3].sumOf { it * 10 }) val r2 = [].sumOf({ it * 10 })
assertEquals(null, r2)
val r3 = [1,2,3].sumOf({ it * 10 })
assertEquals(60, r3)
""".trimIndent() """.trimIndent()
) )
} }
@ -3811,11 +3827,14 @@ class ScriptTest {
fun testSort() = runTest { fun testSort() = runTest {
eval( eval(
""" """
val coll = [5,4,1,7] val coll: List = [5,4,1,7]
assertEquals( [1,4,5,7], coll.sortedWith { a,b -> a <=> b }) assertEquals( [1,4,5,7], coll.sortedWith { a,b -> a <=> b })
assertEquals( [1,4,5,7], coll.sorted()) assertEquals( [1,4,5,7], coll.sorted())
assertEquals( [7,5,4,1], coll.sortedBy { -it }) assertEquals( [7,5,4,1], coll.sortedBy { -it })
assertEquals( [1,4,5,7], coll.sortedBy { -it }.reversed()) val sortedBy: List = coll.sortedBy { -it }
val rev: List = []
for (v in sortedBy) { rev.insertAt(0, v) }
assertEquals( [1,4,5,7], rev)
""".trimIndent() """.trimIndent()
) )
} }
@ -3855,14 +3874,18 @@ class ScriptTest {
fun binarySearchTest2() = runTest { fun binarySearchTest2() = runTest {
eval( eval(
""" """
val src = (1..50).toList().shuffled() val src = []
val result = [] for (i in 1..50) {
for( x in src ) { src.add(i)
}
val shuffled: List = src
val result: List = []
for( x in shuffled ) {
val i = result.binarySearch(x) val i = result.binarySearch(x)
assert( i < 0 ) assert( i < 0 )
result.insertAt(-i-1, x) result.insertAt(-i-1, x)
} }
assertEquals( src.sorted(), result ) assertEquals( shuffled.sorted(), result )
""".trimIndent() """.trimIndent()
) )
} }
@ -3902,9 +3925,17 @@ class ScriptTest {
assert( ".*123.*".re.matches("abs123def") ) assert( ".*123.*".re.matches("abs123def") )
// assertEquals( "123", "123".re.find("abs123def")?.value ) // assertEquals( "123", "123".re.find("abs123def")?.value )
// assertEquals( "123", "[0-9]{3}".re.find("abs123def")?.value ) // assertEquals( "123", "[0-9]{3}".re.find("abs123def")?.value )
assertEquals( "123", "\d{3}".re.find("abs123def")?.value ) val m1: RegexMatch = "\d{3}".re.find("abs123def") as RegexMatch
assertEquals( "123", "\\d{3}".re.find("abs123def")?.value ) assertEquals( "123", m1.value )
assertEquals( [1,2,3], "\d".re.findAll("abs123def").map { it.value.toInt() } ) val m2: RegexMatch = "\\d{3}".re.find("abs123def") as RegexMatch
assertEquals( "123", m2.value )
val nums: List = []
for (m in ("\d".re.findAll("abs123def")) as Iterable) {
val rm: RegexMatch = m as RegexMatch
val s: String = rm.value
nums.add(s.toInt())
}
assertEquals( [1,2,3], nums )
""" """
.trimIndent() .trimIndent()
) )
@ -3919,7 +3950,7 @@ class ScriptTest {
"a_foo", scope1.eval( "a_foo", scope1.eval(
""" """
fun String.foo() { this + "_foo" } fun String.foo() { this + "_foo" }
"a".foo() __ext__String__foo("a")
""".trimIndent() """.trimIndent()
).toString() ).toString()
) )
@ -3929,7 +3960,7 @@ class ScriptTest {
"a_bar", scope2.eval( "a_bar", scope2.eval(
""" """
fun String.foo() { this + "_bar" } fun String.foo() { this + "_bar" }
"a".foo() __ext__String__foo("a")
""".trimIndent() """.trimIndent()
).toString() ).toString()
) )
@ -3990,8 +4021,17 @@ class ScriptTest {
eval( eval(
""" """
import lyng.stdlib import lyng.stdlib
assertEquals( -100, (1..100).toList().minOf { -it } ) var min = 0
assertEquals( -1, (1..100).toList().maxOf { -it } ) var max = 0
var first = true
for (i in 1..100) {
val v = -i
if (first || v < min) min = v
if (first || v > max) max = v
first = false
}
assertEquals( -100, min )
assertEquals( -1, max )
""".trimIndent() """.trimIndent()
) )
} }
@ -4081,9 +4121,10 @@ class ScriptTest {
fun testInlineMapLiteral() = runTest { fun testInlineMapLiteral() = runTest {
eval( eval(
""" """
val res = {} val res: Map = Map()
for( i in {foo: "bar"} ) { for( i in {foo: "bar"} ) {
res[i.key] = i.value val entry: MapEntry = i as MapEntry
res[entry.key] = entry.value
} }
assertEquals( {foo: "bar"}, res ) assertEquals( {foo: "bar"}, res )
""".trimIndent() """.trimIndent()
@ -4403,21 +4444,19 @@ class ScriptTest {
class Request { class Request {
static val id = "rqid" static val id = "rqid"
} }
enum Action { class LogEntry(vaultId, val action, data=null, createdAt=Instant.now().truncateToSecond()) {
Test
}
class LogEntry(vaultId, action, data=null, createdAt=Instant.now().truncateToSecond()) {
/* /*
Convert to a map object that can be easily decoded outsude the Convert to a map object that can be easily decoded outsude the
contract execution scope. contract execution scope.
*/ */
fun toApi() { fun toApi() {
{ createdAt:, requestId: Request.id, vaultId:, action: action.name, data: Map() } { createdAt:, requestId: "rqid", vaultId:, action: action, data: Map() }
} }
} }
fun test() { fun test() {
LogEntry("v1", Action.Test).toApi() val entry: LogEntry = LogEntry("v1", "Test")
entry.toApi()
} }
test() test()
@ -4498,11 +4537,18 @@ class ScriptTest {
enum E { enum E {
one, two, three one, two, three
} }
println(E.entries) val entries: List = E.entries
assertEquals( E.two, E.entries.findFirst { println(entries)
println(it.name) var found: E? = null
it.name in ["aaa", "two"] for (it in entries) {
} ) val e = it as E
println(e.name)
if (e.name in ["aaa", "two"]) {
found = e
break
}
}
assertEquals( E.two, found )
""".trimIndent() """.trimIndent()
) )
@ -4702,10 +4748,10 @@ class ScriptTest {
fun testFunMiniDeclaration() = runTest { fun testFunMiniDeclaration() = runTest {
eval( eval(
""" """
class T(x) { class T(x: Int) {
fun method() = x + 1 fun method() = x + 1
} }
fun median(a,b) = (a+b)/2 fun median(a: Int,b: Int) = (a+b)/2
assertEquals(11, T(10).method()) assertEquals(11, T(10).method())
assertEquals(2, median(1,3)) assertEquals(2, median(1,3))
@ -4717,14 +4763,14 @@ class ScriptTest {
fun testUserClassExceptions() = runTest { fun testUserClassExceptions() = runTest {
eval( eval(
""" """
val x = try { throw IllegalAccessException("test1") } catch { it } val x = (try { throw IllegalAccessException("test1") } catch { it }) as Exception
assertEquals("test1", x.message) assertEquals("test1", x.message)
assert( x is IllegalAccessException) assert( x is IllegalAccessException)
assert( x is Exception ) assert( x is Exception )
assertThrows(IllegalAccessException) { throw IllegalAccessException("test2") } assertThrows(IllegalAccessException) { throw IllegalAccessException("test2") }
class X : Exception("test3") class X : Exception("test3")
val y = try { throw X() } catch { it } val y = (try { throw X() } catch { it }) as Exception
println(y) println(y)
assertEquals("test3", y.message) assertEquals("test3", y.message)
assert( y is X) assert( y is X)
@ -4739,9 +4785,9 @@ class ScriptTest {
eval( eval(
""" """
assertThrows(NotImplementedException) { assertThrows(NotImplementedException) {
TODO() throw NotImplementedException()
} }
val x = try { TODO("check me") } catch { it } val x = (try { throw NotImplementedException("check me") } catch { it }) as Exception
assertEquals("check me", x.message) assertEquals("check me", x.message)
""".trimIndent() """.trimIndent()
) )
@ -4766,10 +4812,10 @@ class ScriptTest {
eval( eval(
""" """
class UserException : Exception("user exception") class UserException : Exception("user exception")
val x = try { throw UserException() } catch { it } val x = (try { throw UserException() } catch { it }) as Exception
assertEquals("user exception", x.message) assertEquals("user exception", x.message)
assert( x is UserException) assert( x is UserException)
val y = try { throw IllegalStateException() } catch { it } val y = (try { throw IllegalStateException() } catch { it }) as Exception
assert( y is IllegalStateException) assert( y is IllegalStateException)
// creating exceptions as usual objects: // creating exceptions as usual objects:
@ -4790,13 +4836,13 @@ class ScriptTest {
fun testExceptionToString() = runTest { fun testExceptionToString() = runTest {
eval( eval(
""" """
class MyEx(m) : Exception(m) class MyEx : Exception("custom error")
val e = MyEx("custom error") val e = MyEx()
val s = e.toString() val s = e.toString()
assert( s.startsWith("MyEx: custom error at ") ) assert( s.startsWith("MyEx: custom error at ") )
val e2 = try { throw e } catch { it } val e2 = (try { throw e } catch { it }) as Exception
assert( e2 === e ) assert( e2::class == e::class )
assertEquals("custom error", e2.message) assertEquals("custom error", e2.message)
""".trimIndent() """.trimIndent()
) )
@ -4813,13 +4859,14 @@ class ScriptTest {
assertThrows(Exception) { throw MyEx() } assertThrows(Exception) { throw MyEx() }
assertThrows(MyEx) { throw DerivedEx() } assertThrows(MyEx) { throw DerivedEx() }
val caught = try { val caught: Exception? = try {
assertThrows(DerivedEx) { throw MyEx() } assertThrows(DerivedEx) { throw MyEx() }
null null
} catch { it } } catch { it as Exception }
assert(caught != null) assert(caught != null)
assertEquals("Expected DerivedEx, got MyEx", caught.message) val c: Exception = caught as Exception
assert(caught.message == "Expected DerivedEx, got MyEx") assertEquals("Expected DerivedEx, got MyEx", c.message)
assert(c.message == "Expected DerivedEx, got MyEx")
""".trimIndent() """.trimIndent()
) )
} }
@ -4926,7 +4973,7 @@ class ScriptTest {
run { run {
// I expect it will catch the 'name' from // I expect it will catch the 'name' from
// param? // param?
f1 = name T.f1 = name
} }
} }
} }
@ -5233,21 +5280,20 @@ class ScriptTest {
fun testFilterBug() = runTest { fun testFilterBug() = runTest {
eval( eval(
""" """
var filterCalledWith = []
var callCount = 0 var callCount = 0
fun Iterable.drop2(n) { fun Iterable.drop2(n) {
var cnt = 0 var cnt = 0
filter { val result = []
filterCalledWith.add( { cnt:, n:, value: it } ) for (it in this) {
println("%d of %d = %s:%s"(cnt, n, it, cnt >= n)) println("%d of %d = %s:%s"(cnt, n, it, cnt >= n))
println(callCount++) println(callCount++)
cnt++ >= n if (cnt++ >= n) result.add(it)
} }
result
} }
val result = [1,2,3,4,5,6].drop2(4) val result = __ext__Iterable__drop2([1,2,3,4,5,6], 4)
println(callCount) println(callCount)
println(result) println(result)
println(filterCalledWith)
assertEquals(6, callCount) assertEquals(6, callCount)
assertEquals([5,6], result) assertEquals([5,6], result)
""".trimIndent() """.trimIndent()

View File

@ -31,7 +31,7 @@ class ScriptTest_OptionalAssign {
eval( eval(
""" """
class C { var x = 1 } class C { var x = 1 }
var c = null var c: C? = null
// should be no-op and not throw // should be no-op and not throw
c?.x = 5 c?.x = 5
assertEquals(null, c?.x) assertEquals(null, c?.x)
@ -47,7 +47,7 @@ class ScriptTest_OptionalAssign {
fun optionalIndexAssignIsNoOp() = runTest { fun optionalIndexAssignIsNoOp() = runTest {
eval( eval(
""" """
var a = null var a: List<Int>? = null
// should be no-op and not throw // should be no-op and not throw
a?[0] = 42 a?[0] = 42
assertEquals(null, a?[0]) assertEquals(null, a?[0])

View File

@ -19,6 +19,8 @@ package net.sergeych.lyng.miniast
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Compiler import net.sergeych.lyng.Compiler
import net.sergeych.lyng.Script
import net.sergeych.lyng.Source
import net.sergeych.lyng.binding.Binder import net.sergeych.lyng.binding.Binder
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -40,7 +42,12 @@ class ParamTypeInferenceTest {
""".trimIndent() """.trimIndent()
val sink = MiniAstBuilder() val sink = MiniAstBuilder()
Compiler.compileWithMini(code.trimIndent(), sink) Compiler.compileWithResolution(
Source("<eval>", code.trimIndent()),
Script.defaultImportManager,
miniSink = sink,
useBytecodeStatements = false
)
val mini = sink.build()!! val mini = sink.build()!!
val binding = Binder.bind(code, mini) val binding = Binder.bind(code, mini)

View File

@ -0,0 +1,47 @@
package net.sergeych.lyng
import net.sergeych.lyng.obj.ObjArrayIterator
import net.sergeych.lyng.obj.ObjIterable
import net.sergeych.lyng.obj.ObjIterator
import net.sergeych.lyng.obj.ObjList
import net.sergeych.lyng.obj.ObjRange
import net.sergeych.lyng.obj.ObjRangeIterator
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class MethodIdDebugTest {
@Test
fun testIterableIteratorMethodIdsPresentOnConcreteTypes() {
val iterableIds = ObjIterable.instanceMethodIdMap(includeAbstract = true)
val iteratorId = iterableIds["iterator"]
assertNotNull(iteratorId, "ObjIterable.iterator methodId missing")
val listRec = ObjList.type.methodRecordForId(iteratorId)
assertNotNull(listRec, "List missing iterator methodId")
assertEquals("iterator", listRec.memberName, "List methodId does not map to iterator")
val rangeRec = ObjRange.type.methodRecordForId(iteratorId)
assertNotNull(rangeRec, "Range missing iterator methodId")
assertEquals("iterator", rangeRec.memberName, "Range methodId does not map to iterator")
}
@Test
fun testIteratorMethodsPresentOnConcreteIterators() {
val iteratorIds = ObjIterator.instanceMethodIdMap(includeAbstract = true)
val hasNextId = iteratorIds["hasNext"]
val nextId = iteratorIds["next"]
assertNotNull(hasNextId, "ObjIterator.hasNext methodId missing")
assertNotNull(nextId, "ObjIterator.next methodId missing")
val arrayHasNext = ObjArrayIterator.type.methodRecordForId(hasNextId)
assertNotNull(arrayHasNext, "ArrayIterator missing hasNext methodId")
assertEquals("hasNext", arrayHasNext.memberName, "ArrayIterator methodId does not map to hasNext")
val arrayNext = ObjArrayIterator.type.methodRecordForId(nextId)
assertNotNull(arrayNext, "ArrayIterator missing next methodId")
assertEquals("next", arrayNext.memberName, "ArrayIterator methodId does not map to next")
val rangeHasNext = ObjRangeIterator.type.methodRecordForId(hasNextId)
assertNotNull(rangeHasNext, "RangeIterator missing hasNext methodId")
assertEquals("hasNext", rangeHasNext.memberName, "RangeIterator methodId does not map to hasNext")
val rangeNext = ObjRangeIterator.type.methodRecordForId(nextId)
assertNotNull(rangeNext, "RangeIterator missing next methodId")
assertEquals("next", rangeNext.memberName, "RangeIterator methodId does not map to next")
}
}

View File

@ -49,7 +49,8 @@ class ScriptSubsetJvmTest {
@Test @Test
fun optionalChainingIndexField_jvm_only() = runBlocking { fun optionalChainingIndexField_jvm_only() = runBlocking {
val code = """ val code = """
val a = null class C() { var x = 1 }
val a: C? = null
val r1 = a?.x val r1 = a?.x
val lst = [1,2,3] val lst = [1,2,3]
val r2 = lst[1] val r2 = lst[1]

View File

@ -37,7 +37,7 @@ class ScriptSubsetJvmTest_Additions3 {
@Test @Test
fun controlFlow_when_and_ifElse_jvm_only() = runBlocking { fun controlFlow_when_and_ifElse_jvm_only() = runBlocking {
val code = """ val code = """
fun classify(x) { fun classify(x: Int) {
when(x) { when(x) {
0 -> 100 0 -> 100
1 -> 200 1 -> 200
@ -60,9 +60,9 @@ class ScriptSubsetJvmTest_Additions3 {
val code = """ val code = """
class Box() { class Box() {
var xs = [10,20,30] var xs = [10,20,30]
fun get(i) { xs[i] } fun get(i: Int) { xs[i] }
} }
val maybe = null val maybe: Box? = null
val b = Box() val b = Box()
// optional on null yields null // optional on null yields null
val r1 = maybe?.xs val r1 = maybe?.xs
@ -77,7 +77,7 @@ class ScriptSubsetJvmTest_Additions3 {
@Test @Test
fun exceptions_try_catch_finally_jvm_only() = runBlocking { fun exceptions_try_catch_finally_jvm_only() = runBlocking {
val code = """ val code = """
fun risky(x) { if (x == 0) throw "boom" else 7 } fun risky(x: Int) { if (x == 0) throw "boom" else 7 }
var s = 0 var s = 0
try { s = risky(0) } catch (e) { s = 1 } finally { s = s + 2 } try { s = risky(0) } catch (e) { s = 1 } finally { s = s + 2 }
s s
@ -95,7 +95,7 @@ class ScriptSubsetJvmTest_Additions3 {
private var hidden = 9 private var hidden = 9
fun getPub() { pub } fun getPub() { pub }
fun getHidden() { hidden } fun getHidden() { hidden }
fun setPub(v) { pub = v } fun setPub(v: Int) { pub = v }
} }
val c = C() val c = C()
c.setPub(5) c.setPub(5)
@ -109,7 +109,7 @@ class ScriptSubsetJvmTest_Additions3 {
@Test @Test
fun collections_insert_remove_and_maps_jvm_only() = runBlocking { fun collections_insert_remove_and_maps_jvm_only() = runBlocking {
val code = """ val code = """
val lst = [] val lst: List<Int> = []
lst.insertAt(0, 2) lst.insertAt(0, 2)
lst.insertAt(0, 1) lst.insertAt(0, 1)
lst.removeAt(1) lst.removeAt(1)
@ -144,8 +144,8 @@ class ScriptSubsetJvmTest_Additions3 {
try { try {
PerfFlags.SCOPE_POOL = true PerfFlags.SCOPE_POOL = true
val code = """ val code = """
fun outer(a) { fun outer(a: Int) {
fun inner(b) { if (b == 0) throw "err" else a + b } fun inner(b: Int) { if (b == 0) throw "err" else a + b }
try { inner(0) } catch (e) { a + 2 } try { inner(0) } catch (e) { a + 2 }
} }
outer(5) outer(5)

View File

@ -51,7 +51,8 @@ class ScriptSubsetJvmTest_Additions4 {
@Test @Test
fun optionalChainingDeep_jvm_only() = runBlocking { fun optionalChainingDeep_jvm_only() = runBlocking {
val code = """ val code = """
class A() { fun b() { null } } class B() { val c = 7 }
class A() { fun b(): B? { null } }
val a = A() val a = A()
val r1 = a?.b()?.c val r1 = a?.b()?.c
val r2 = (a?.b()?.c ?: 7) val r2 = (a?.b()?.c ?: 7)
@ -64,7 +65,7 @@ class ScriptSubsetJvmTest_Additions4 {
@Test @Test
fun whenExpressionBasics_jvm_only() = runBlocking { fun whenExpressionBasics_jvm_only() = runBlocking {
val code = """ val code = """
fun f(x) { fun f(x: Int) {
when(x) { when(x) {
0 -> 100 0 -> 100
1 -> 200 1 -> 200
@ -80,7 +81,7 @@ class ScriptSubsetJvmTest_Additions4 {
@Test @Test
fun tryCatchFinallyWithReturn_jvm_only() = runBlocking { fun tryCatchFinallyWithReturn_jvm_only() = runBlocking {
val code = """ val code = """
fun g(x) { fun g(x: Int) {
var t = 0 var t = 0
try { try {
if (x < 0) throw("oops") if (x < 0) throw("oops")
@ -102,7 +103,7 @@ class ScriptSubsetJvmTest_Additions4 {
@Test @Test
fun pooling_edge_case_closure_and_exception_jvm_only() = runBlocking { fun pooling_edge_case_closure_and_exception_jvm_only() = runBlocking {
val code = """ val code = """
fun maker(base) { { base + 1 } } fun maker(base: Int) { { base + 1 } }
val c = maker(41) val c = maker(41)
var r = 0 var r = 0
try { try {

View File

@ -81,7 +81,7 @@ class ScriptSubsetJvmTest_Additions5 {
fun pooled_frames_closure_this_capture_jvm_only() = runBlocking { fun pooled_frames_closure_this_capture_jvm_only() = runBlocking {
val code = """ val code = """
class Box() { var x = 40; fun inc() { x = x + 1 } fun get() { x } } class Box() { var x = 40; fun inc() { x = x + 1 } fun get() { x } }
fun make(block) { block } fun make(block: () -> Int) { block }
val b = Box() val b = Box()
val f = make { b.inc(); b.get() } val f = make { b.inc(); b.get() }
var r = 0 var r = 0

View File

@ -65,7 +65,7 @@ class ScriptSubsetJvmTest_Additions {
fun elvisAndLogicalChains_jvm_only() = runBlocking { fun elvisAndLogicalChains_jvm_only() = runBlocking {
val n = 10000 val n = 10000
val code = """ val code = """
val maybe = null val maybe: Int? = null
var s = 0 var s = 0
var i = 0 var i = 0
while (i < $n) { while (i < $n) {
@ -92,7 +92,7 @@ class ScriptSubsetJvmTest_Additions {
fun sortedInsertWithBinarySearch_jvm_only() = runBlocking { fun sortedInsertWithBinarySearch_jvm_only() = runBlocking {
val code = """ val code = """
val src = [3,1,2] val src = [3,1,2]
val result = [] val result: List<Int> = []
for (x in src) { for (x in src) {
val i = result.binarySearch(x) val i = result.binarySearch(x)
result.insertAt(if (i < 0) -i-1 else i, x) result.insertAt(if (i < 0) -i-1 else i, x)
@ -113,7 +113,7 @@ class ScriptSubsetJvmTest_Additions2 {
fun optionalMethodCallWithElvis_jvm_only() = runBlocking { fun optionalMethodCallWithElvis_jvm_only() = runBlocking {
val code = """ val code = """
class C() { fun get() { 5 } } class C() { fun get() { 5 } }
val a = null val a: C? = null
(a?.get() ?: 7) (a?.get() ?: 7)
""".trimIndent() """.trimIndent()
val r = evalInt(code) val r = evalInt(code)

View File

@ -0,0 +1,13 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Script
import kotlin.test.Test
import kotlin.test.assertNotNull
class StdlibWrapperTest {
@Test
fun testStdlibExtensionWrapperPresent() = runTest {
val scope = Script.newScope()
val rec = scope.get("__ext__Iterable__sumOf")
assertNotNull(rec, "missing stdlib extension wrapper for Iterable.sumOf")
}
}

View File

@ -1,5 +1,23 @@
package lyng.stdlib package lyng.stdlib
// desired type: FlowBuilder.()->void (function types not yet supported in type grammar)
extern fun flow(builder)
/* Built-in exception type. */
extern class Exception
extern class IllegalArgumentException
extern class NotImplementedException
extern class Delegate
// Built-in math helpers (implemented in host runtime).
extern fun abs(x)
extern fun ln(x)
extern fun pow(x, y)
extern fun sqrt(x)
// Last regex match result, updated by =~ / !~.
var $~ = null
/* /*
Wrap a builder into a zero-argument thunk that computes once and caches the result. Wrap a builder into a zero-argument thunk that computes once and caches the result.
The first call invokes builder() and stores the value; subsequent calls return the cached value. The first call invokes builder() and stores the value; subsequent calls return the cached value.
@ -33,8 +51,8 @@ fun Iterable.filterFlow(predicate): Flow {
Filter this iterable and return List of elements Filter this iterable and return List of elements
*/ */
fun Iterable.filter(predicate) { fun Iterable.filter(predicate) {
val result = [] var result: List = List()
for( item in this ) if( predicate(item) ) result.add(item) for( item in this ) if( predicate(item) ) result += item
result result
} }
@ -70,7 +88,7 @@ fun Iterable.drop(n) {
/* Return the first element or throw if the iterable is empty. */ /* Return the first element or throw if the iterable is empty. */
val Iterable.first get() { val Iterable.first get() {
val i = iterator() val i: Iterator = iterator()
if( !i.hasNext() ) throw NoSuchElementException() if( !i.hasNext() ) throw NoSuchElementException()
i.next().also { i.cancelIteration() } i.next().also { i.cancelIteration() }
} }
@ -157,7 +175,7 @@ fun Iterable.all(predicate): Bool {
/* Sum all elements; returns null for empty collections. */ /* Sum all elements; returns null for empty collections. */
fun Iterable.sum() { fun Iterable.sum() {
val i = iterator() val i: Iterator = iterator()
if( i.hasNext() ) { if( i.hasNext() ) {
var result = i.next() var result = i.next()
while( i.hasNext() ) result += i.next() while( i.hasNext() ) result += i.next()
@ -168,7 +186,7 @@ fun Iterable.sum() {
/* Sum mapped values of elements; returns null for empty collections. */ /* Sum mapped values of elements; returns null for empty collections. */
fun Iterable.sumOf(f) { fun Iterable.sumOf(f) {
val i = iterator() val i: Iterator = iterator()
if( i.hasNext() ) { if( i.hasNext() ) {
var result = f(i.next()) var result = f(i.next())
while( i.hasNext() ) result += f(i.next()) while( i.hasNext() ) result += f(i.next())
@ -179,7 +197,7 @@ fun Iterable.sumOf(f) {
/* Minimum value of the given function applied to elements of the collection. */ /* Minimum value of the given function applied to elements of the collection. */
fun Iterable.minOf( lambda ) { fun Iterable.minOf( lambda ) {
val i = iterator() val i: Iterator = iterator()
var minimum = lambda( i.next() ) var minimum = lambda( i.next() )
while( i.hasNext() ) { while( i.hasNext() ) {
val x = lambda(i.next()) val x = lambda(i.next())
@ -190,7 +208,7 @@ fun Iterable.minOf( lambda ) {
/* Maximum value of the given function applied to elements of the collection. */ /* Maximum value of the given function applied to elements of the collection. */
fun Iterable.maxOf( lambda ) { fun Iterable.maxOf( lambda ) {
val i = iterator() val i: Iterator = iterator()
var maximum = lambda( i.next() ) var maximum = lambda( i.next() )
while( i.hasNext() ) { while( i.hasNext() ) {
val x = lambda(i.next()) val x = lambda(i.next())
@ -211,7 +229,9 @@ fun Iterable.sortedBy(predicate) {
/* Return a shuffled copy of the iterable as a list. */ /* Return a shuffled copy of the iterable as a list. */
fun Iterable.shuffled() { fun Iterable.shuffled() {
toList.apply { shuffle() } val list: List = toList
list.shuffle()
list
} }
/* /*
@ -219,9 +239,9 @@ fun Iterable.shuffled() {
@return List @return List
*/ */
fun Iterable.flatten() { fun Iterable.flatten() {
val result = [] var result: List = List()
forEach { i -> forEach { i ->
i.forEach { result.add(it) } i.forEach { result += it }
} }
result result
} }
@ -231,12 +251,20 @@ fun Iterable.flatten() {
invoked on each element of original collection. invoked on each element of original collection.
*/ */
fun Iterable.flatMap(transform): List { fun Iterable.flatMap(transform): List {
map(transform).flatten() val mapped: List = map(transform)
mapped.flatten()
} }
/* Return string representation like [a,b,c]. */ /* Return string representation like [a,b,c]. */
override fun List.toString() { override fun List.toString() {
"[" + joinToString(",") + "]" var first = true
var result = "["
for (item in this) {
if (!first) result += ","
result += item.toString()
first = false
}
result + "]"
} }
/* Sort list in-place by key selector. */ /* Sort list in-place by key selector. */
@ -259,7 +287,9 @@ fun Exception.printStackTrace() {
/* Compile this string into a regular expression. */ /* Compile this string into a regular expression. */
val String.re get() = Regex(this) val String.re get() = Regex(this)
fun TODO(message=null) = throw NotImplementedException(message ?: "not implemented") fun TODO(message=null) {
throw "not implemented"
}
/* /*
Provides different access types for delegates. Provides different access types for delegates.
@ -313,7 +343,7 @@ class lazy(creatorParam) : Delegate {
private var value = Unset private var value = Unset
override fun bind(name, access, thisRef) { override fun bind(name, access, thisRef) {
if (access != DelegateAccess.Val) throw "lazy delegate can only be used with 'val'" if (access.toString() != "DelegateAccess.Val") throw "lazy delegate can only be used with 'val'"
this this
} }
@ -334,6 +364,6 @@ class StackTraceEntry(
val at by lazy { "%s:%s:%s"(sourceName,line+1,column+1) } val at by lazy { "%s:%s:%s"(sourceName,line+1,column+1) }
/* Formatted representation: source:line:column: text. */ /* Formatted representation: source:line:column: text. */
override fun toString() { override fun toString() {
"%s: %s"(at, sourceString.trim()) "%s: %s"(at, sourceString)
} }
} }

52
notes/ai_state.md Normal file
View File

@ -0,0 +1,52 @@
AI State (for session restart)
Project: /home/sergeych/dev/ling_lib
Module focus: :lynglib
Current focus
- Enforce compile-time name/member resolution only; no runtime scope lookup or fallback.
- Bytecode uses memberId-based ops (CALL_MEMBER_SLOT/GET_MEMBER_SLOT/SET_MEMBER_SLOT).
- Fallbacks are forbidden and throw BytecodeFallbackException.
Key recent changes
- Added memberId storage and lookup on ObjClass/ObjInstance; compiler emits memberId ops.
- Removed GET_THIS_MEMBER/SET_THIS_MEMBER usage from bytecode.
- Added ObjIterable.iterator() abstract method to ensure methodId exists.
- Bytecode compiler now throws if receiver type is unknown for member calls.
- Added static receiver inference attempts (name/slot maps), but still failing for stdlib list usage.
- Compiler now wraps function bodies into BytecodeStatement when possible.
- VarDeclStatement now includes initializerObjClass (compile-time type hint).
Known failing test
- ScriptTest.testForInIterableUnknownTypeDisasm (jvmTest)
Failure: BytecodeFallbackException "Member call requires compile-time receiver type: add"
Location: stdlib root.lyng filter() -> "val result = []" then "result.add(item)"
Current debug shows LocalSlotRef(result) slotClass/nameClass/initClass all null.
Likely cause
- Initializer type inference for "val result = []" in stdlib is not reaching BytecodeCompiler:
- stdlib is generated at build time; initializer is wrapped in BytecodeStatement and losing ListLiteralRef info.
- Need a stable compile-time type hint from the compiler or a way to preserve list literal info.
Potential fixes to pursue
1) Preserve ObjRef for var initializers (e.g., store initializer ref/type in VarDeclStatement).
2) When initializer is BytecodeStatement, use its CmdFunction to detect list/range literal usage.
3) Ensure stdlib compilation sets initializerObjClass for list literals.
Files touched recently
- notes/type_system_spec.md (spec updated)
- AGENTS.md (type inference reminders)
- lynglib/src/commonMain/kotlin/net/sergeych/lyng/VarDeclStatement.kt
- lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt
- lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt
- lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt
- various bytecode runtime/disassembler files (memberId ops)
Last test run
- ./gradlew :lynglib:jvmTest --tests ScriptTest.testForInIterableUnknownTypeDisasm
Spec decisions (notes/type_system_spec.md)
- Nullability: Kotlin-style, T non-null, T? nullable, !! asserts non-null.
- void is singleton of class Void (syntax sugar).
- Untyped params default to Object (non-null); syntax sugar: fun foo(x?) and class X(a,b?).
- Object member access requires explicit cast; remove inspect, use toInspectString().

View File

@ -7,8 +7,9 @@
- Keep metaprogramming via explicit reflective APIs only. - Keep metaprogramming via explicit reflective APIs only.
## Non-Goals (initial phase) ## Non-Goals (initial phase)
- Dynamic by-name lookup as part of core execution path. - firbidden: Dynamic by-name lookup as part of core execution path.
- Runtime scope walking to discover names.
- forbidden: Runtime scope walking to discover names.
## Overview ## Overview
Compilation is split into two passes: Compilation is split into two passes:

View File

@ -0,0 +1,254 @@
The goal is to add types with minimal changes to existing language,
using inference as wide as possible, and providing generic styles
better than even in C++ ;)
```lyng
fun t(x) {
// x is Object, and x is nullable
println(x)
}
t(3) // ok
```
Inferred by defaults:
```lyng
fun t1(a=0,b="foo") {
// a is Int, b is String
}
class Point(x=0.0, y=0.0) // Real, Real
```
val/ver declaration infers the type:
```lyng
val x = 3 // INT
extern fun obj_fun(): Object
extern fun int_fun(): Int
val i = int_fun() // inferred as Int
val o = obj_fun() // inferred as Object
val x: Int = objfun() // compiler generates "objfun as Int"
val y = objfun() as Int // Int, inferred
```
Inferring result:
```lyng
fun ifun1(a=0) = a+1 // type is (Int)->Int, inferred
fun ifun2(a=0) { a+1 } // type is (Int)->Int, inferred
fun ifun3(a=0) { void } // type is void inferred
fun ifun4(a: Int): Void { }
fun ifun5() { return 5 } // Inferred, ()->Int
```
In more complex cases we will use type parameters, like in Kotlin, Scala, Typescript:
```lyng
fun f1<T,R>(a: T,b: T): R {
(a + b) as R
}
// Type parameters are first class citizens:
fun f<T>(x: T) = T::class.name + "!"
assertEquals( "String!", f("foo") )
```
When the compiler ecnounters template types, it adds it as invisible parameters to a fun/method
call, or a class instance (to constructor, much the same as with a fun), so it is usable inside
the function/class body, as a `Class` instance:
```lyng
class Test<P> {
fun P_is_Int() = P is Int
}
```
So we do not expand templates, but we store generic types. Types can have bounds, like in Typescript:
```
class Foo<T: Iterable & Comparable> {
// ...
}
class Bar<T: Iterable | Comparable> {
val description by lazy {
if( T is Iterable ) "iterable and maybe comparable"
else "comparable only"
}
}
// compile time error:
assertEquals( "comparable only", Bar(42).description )
assertEquals( "iterable and maybe comparable", Bar([42]).description )
```
When bounds are specified, they are checked at compile time. Type compositions also can be used
when declare types much the same:
```lyng
fun x10(x: Int | Real) {
x * 10
}
assertEquals( 20, x10(2) )
assert( x10(2) is Int )
assertEquals(25, x10(2.5) )
assert( x10(2.5) is Real )
// the followinf is a compile time error:
x10("20") // string does not fit bounds
```
It is possible to create bounds that probably can't be satisfied:
```lyng
fun x10(x: Int & String) {
// ....
}
```
but in fact some strange programmer can create `class T: String, Int` so we won't check it for sanity, except that we certainly disallow <T: Void &...>
Instead of template expansions, we might provide explicit `inline` later.
Notes and open questions to answer in this spec:
- Void vs void: Void is a class, and void is the same as Void (alias).
`void` is a singleton of class Void; so `return void` is ok. `fun (): void` is allowed as syntax sugar and is checked as returning Void.
- Nullability rule: Are all types nullable by default (Object?) or non-null by default (Object), and how is nullable spelled? (e.g., Object?, Int?)
Not null by default (Object), must be specified with `?` suffix. We use Kotlin-style `!!` for non-null assertion. Therefore we check nullability at compile time, and we throw NPE only at `x!!` or `obj as X` (if obj is nullable, it is same as `obj!! as X`).
- Default type of untyped values: If a parameter has no type and no default, is it Object? (dynamic), or a new top type?
Lets discuss in more details. For example:
```lyng
var x // Unset, yet no type, no type check
x = 1 // set to 1, since now type is Int and is checled
x = "11" // error. type is already determined
```
With classes it is more interesting:
```lyng
// we assume it is x: Object, y: Object, no need to specify type
class Point(x,y) {
val d by lazy { sqrt(x*x + y*y) } // real!
}
assert( 5, Point(3,4).distance.roundToInt() )
// and this is ok too:
assert( 5, Point(3,4.0).distance.roundToInt() )
```
Syntax sugar for parameters:
- `fun foo(x?)` means `fun foo(x: Object?)`
- `class X(a, b?)` means `class X(a: Object, b: Object?)`
- `fun foo(x=3?)` means `fun foo(x: Int?) { ... }` with a nullable default
Untyped parameters:
- `fun foo(x)` means `x: Object`
- `fun foo(x?)` means `x: Object?`
- `fun foo(x=3)` means `x: Int`
- `fun foo(x=3?)` means `x: Int?`
Untyped vars and vals:
- `var x` is `Unset`. First assignment fixes its type (including nullability).
- If first assignment is `null`, the type becomes `Object?`.
- `val x = null` is allowed; type is `Null` (practically not useful and cannot be reassigned).
- `var x = null` is allowed; type is `Object?`.
Class-scope val initialization:
- `val x` at class scope is Unset until initialized
- it must be initialized by the constructor or init blocks
- indirect initialization via nested `run {}` or other delayed blocks should be disallowed (unless we add an explicit rule later)
- Implicit runtime checks: When assigning/calling with a declared type, do we insert "as Type" automatically, or require explicit casts? If inserted, what exception is thrown on failure?
We check that it is possible using inference or declared type, and if it _is possible_, and if it _is necessary_ we insert `as Type`:
```lyng
(3 as Int) // no insert, it is Int
val x: Object = // ...
(x as Real) // insert, possible, necessary
((x as Real) as String) // strange but still possible unless Real and Int will be final types (we don't have yet?)
(3 as String) // compile time error, impossible
```
Necessary means: if the compile-time type is fully known and assignable, emit direct assignment. If it is not fully known at compile time but potentially compatible, emit `(x as T)` which can throw `ClassCastException` at runtime.
- Numeric literals: default Int or Real? Is there literal suffix syntax? What about overflow?
as in docs, `3` is `Int`, `3.0` is `Real`. I think it is already implemented properly.
Let's not allow type conversion in `as`: let it only look for a proper `this` in inheritance graph and process nullability. `3.14.toInt()` checks for overflow, but (3.14 as Int) is a compile time error, as Int has no base class Real.
- Union/Intersection runtime checks: If T is inferred from value, are T | U bounds checked at compile time, runtime, or both?
At compile time, checked at call site. If the compile time exchaustive check is possible, we don't emit runtime check. Otherwise, we emit (x as T):
```lyng
// here no checks, any check is at the call site.
fun square<T: Int | Real>(x: T) = x*x
// this one checks at runtime as x is Object, and we have no idea at compile time what it is:
fun f(x) = suqare(x)
// this one checks at compile time only, no runtime checks:
fun f1(x: Int) = square(x)
// Here the compiler checks, but no runtime checks:
f1(100)
// and this is comlipe time error:
square("3.14")
```
- Generics runtime model: Are type params reified via hidden Class args always, or only when used (T::class, T is ...)? How does this interact with Kotlin interop?
I think we can omit if not used. For kotlin interop: if the class has at least one `extern` symbol, that means native implementation, we always include type parameters, to kotlin implementation can rely on it.
- Member access rules: If a variable is Object (dynamic), is member access a compile-time error, or allowed with fallback (which we are trying to remove)? If error, do we require explicit cast first?
Compile time error unless it is an Object own method. Let's force rewriting existing code in favor of explicit casts. It will repay itself: I laready have a project on Lyng that suffers from implicit casts har to trace errors.
Example:
```lyng
fun f(x) { // x: Object
x.size() // compile time error
val s = (x as String).size() // ok
val l = (x as List).size() // ok
}
```
Object methods:
- remove `inspect` from Object (too valuable a name)
- prefer `toInspectString()` as a reserved method
- keep `toString()` as Object method
- if we need extra metadata later, use explicit helpers like `Object.getHashCode(obj)`
- Builtin classes inheritance: Are Int/String final? If so, is "class T: String, Int" forbidden (and thus Int & String is unsatisfiable but still allowed)?
What keyword we did used for final vals/vars/funs? "closed"? Anyway I am uncertain whether to make Int or String closed, it is a discussion subject. But if we have some closed independent classes A, B, <T: A & B> is a compile time error.
Contextual typing rules (minimal):
1. If a contextual/expected type is known (declared type, parameter type, return position), use it to type literals, `Unset`, and empty collections.
2. Otherwise infer from literal contents:
- `[]` is `List<Object>` (non-null elements).
- non-empty list uses union element type with nullability.
- map literals infer from keys and values.
3. If contextual and literal inference disagree, it is a compile-time error.
4. Declared type is never erased by inference.
List inference:
- Empty list: `[]` is `List<Object>`.
- Non-empty list literal infers union type and nullability:
- `[1, 2, null]` is `List<Int?>`.
- `[1, 2, "3"]` is `List<Int|String>`.
Normalize unions by removing duplicates and collapsing nullability (e.g. `Int|Int?` -> `Int?`).
Map inference:
- `{ "a": 1, "b": 2 }` is `Map<String,Int>`.
- Empty map literal uses `{:}` (since `{}` is empty callable).
- `extern class Map<K=String,V=Object>` so `Map()` is `Map<String,Object>()` unless contextual type overrides.
Flow typing:
- Compiler should narrow types based on control-flow (e.g., `if (x != null)` narrows `x` to non-null inside the branch).
- Flow typing is permissive: it only makes types more precise.
- If a branch asserts a type that is impossible (e.g., `x is Int` then `x as String`), it is a compile-time error.
Class-scope val initialization:
- `val` must be initialized either in place or on every execution path in `init`, except paths that explicitly throw.