Step 1: module import slots and extension lookup

This commit is contained in:
Sergey Chernov 2026-02-08 14:04:25 +03:00
parent a557d0cc59
commit 7b70a37e90
14 changed files with 954 additions and 289 deletions

View File

@ -1,41 +1,32 @@
# Bytecode migration plan (compiler -> frames/bytecode) # Bytecode Migration Plan
This is a step-by-step checklist to track remaining non-bytecode paths in the compiler. Goal: migrate :lynglib compiler/runtime so values live in frame slots and bytecode is the default execution path.
Mark items as you implement them. Priorities are ordered by expected simplicity.
## Priority 1: Quick wins (local changes) ## Step 1: Imports as module slots (done)
- [ ] Implement bytecode emission for `DelegatedVarDeclStatement`. - [x] Seed module slot plans from import bindings (lazy, unused imports do not allocate).
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt:3284`, `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1667` - [x] Avoid mutating scopes during compile-time imports; bind slots at runtime instead.
- [ ] Implement bytecode emission for `DestructuringVarDeclStatement`. - [x] Make runtime member access honor extensions (methods + properties).
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt:3285`, `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1667` - [x] Ensure class members (ObjClass instances) resolve by slot id in bytecode runtime.
- [ ] Ensure `ExtensionPropertyDeclStatement` is handled in both value and no-value bytecode paths. - [x] Expose `Iterator` in root scope so stdlib externs bind at runtime.
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt:3332`
## Priority 2: Conservative wrapper guards to relax ## Step 2: Class-scope member refs + qualified-this refs (pending)
- [ ] Allow wrapping `BreakStatement` / `ContinueStatement` / `ReturnStatement` where bytecode already supports them. - [ ] Bytecode-compile `ClassScopeMemberRef` (currently forced to AST in `Compiler.containsUnsupportedRef`).
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1608` - [ ] Bytecode-compile `QualifiedThisFieldSlotRef` / `QualifiedThisMethodSlotCallRef`.
- [ ] Revisit `containsLoopControl` as a hard blocker for wrapping (once label handling is verified). - [ ] Ensure slot resolution uses class member ids, not scope lookup; no fallback opcodes.
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1542` - [ ] Add coverage for class static access + qualified-this access to keep JVM tests green.
## Priority 3: Medium complexity statements ## Step 3: Expand bytecode coverage for control flow + literals (pending)
- [ ] Implement bytecode support for `TryStatement`. - [ ] Add bytecode support for `TryStatement` (catch/finally) in `Compiler.containsUnsupportedForBytecode` and `BytecodeCompiler`.
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1698`, `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt:3290` - [ ] Support `WhenStatement` conditions beyond the current limited set.
- [ ] Expand `WhenStatement` condition coverage (remove "unsupported condition" paths). - [ ] Add map literal spread support (currently throws in `BytecodeCompiler`).
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1699` - [ ] Remove remaining `BytecodeCompileException` cases for common member access (missing id paths).
## Priority 4: Ref-level blockers ## Known bytecode gaps (from current guards)
- [ ] Support dynamic member access in bytecode (`FieldRef` / `MethodCallRef` on `ObjDynamic`). - [ ] `TryStatement` is always excluded by `Compiler.containsUnsupportedForBytecode`.
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1735` - [ ] `ClassScopeMemberRef` and qualified-this refs are excluded by `Compiler.containsUnsupportedRef`.
- [ ] Implement bytecode for qualified/captured member refs: - [ ] `BytecodeCompiler` rejects map literal spreads and some argument expressions.
- `QualifiedThisMethodSlotCallRef` - [ ] Member access still fails when compile-time receiver class cannot be resolved.
- `QualifiedThisFieldSlotRef`
- `ClassScopeMemberRef`
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1759`
## Priority 5: Wrapping policy improvements ## Validation
- [ ] Allow partial script wrapping (wrap supported statements even when others are unsupported). - [ ] `./gradlew :lynglib:jvmTest` (full suite) after each step; if failures pre-exist, run targeted tests tied to the change and record the gap in this file.
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1333` - [ ] Baseline full suite: currently 46 failures on `:lynglib:jvmTest` (run 2026-02-08); keep targeted tests green until the baseline is addressed.
- [ ] Re-evaluate tooling paths that disable bytecode:
- `CompileTimeResolution.dryRun` (resolution-only)
- `LyngLanguageTools.analyze` (diagnostics/mini-ast)
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/resolution/CompileTimeResolution.kt:67`, `lynglib/src/commonMain/kotlin/net/sergeych/lyng/tools/LyngLanguageTools.kt:97`

View File

@ -37,8 +37,14 @@ class BlockStatement(
?: applyScope.callScope.resolveCaptureRecord(capture.name) ?: applyScope.callScope.resolveCaptureRecord(capture.name)
} else { } else {
scope.resolveCaptureRecord(capture.name) scope.resolveCaptureRecord(capture.name)
} ?: (applyScope?.callScope ?: scope) }
if (rec == null) {
if (scope.getSlotIndexOf(capture.name) == null && scope.getLocalRecordDirect(capture.name) == null) {
continue
}
(applyScope?.callScope ?: scope)
.raiseSymbolNotFound("symbol ${capture.name} not found") .raiseSymbolNotFound("symbol ${capture.name} not found")
}
target.updateSlotFor(capture.name, rec) target.updateSlotFor(capture.name, rec)
} }
} }

View File

@ -107,6 +107,12 @@ class Compiler(
if (added && localDeclCountStack.isNotEmpty()) { if (added && localDeclCountStack.isNotEmpty()) {
localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1 localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1
} }
capturePlanStack.lastOrNull()?.let { plan ->
if (plan.captureMap.remove(name) != null) {
plan.captureOwners.remove(name)
plan.captures.removeAll { it.name == name }
}
}
declareSlotName(name, isMutable, isDelegated) declareSlotName(name, isMutable, isDelegated)
} }
@ -124,6 +130,20 @@ class Compiler(
plan.nextIndex += 1 plan.nextIndex += 1
} }
private fun declareSlotNameAt(
plan: SlotPlan,
name: String,
slotIndex: Int,
isMutable: Boolean,
isDelegated: Boolean
) {
if (plan.slots.containsKey(name)) return
plan.slots[name] = SlotEntry(slotIndex, isMutable, isDelegated)
if (slotIndex >= plan.nextIndex) {
plan.nextIndex = slotIndex + 1
}
}
private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull() private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull()
private val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf() private val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
private val nameObjClass: MutableMap<String, ObjClass> = mutableMapOf() private val nameObjClass: MutableMap<String, ObjClass> = mutableMapOf()
@ -211,6 +231,20 @@ class Compiler(
} }
} }
private fun seedSlotPlanFromSeedScope(scope: Scope) {
val plan = moduleSlotPlan() ?: return
for ((name, slotIndex) in scope.slotNameToIndexSnapshot()) {
val record = scope.getSlotRecord(slotIndex)
declareSlotNameAt(
plan,
name,
slotIndex,
record.isMutable,
record.type == ObjRecord.Type.Delegated
)
}
}
private fun predeclareTopLevelSymbols() { private fun predeclareTopLevelSymbols() {
val plan = moduleSlotPlan() ?: return val plan = moduleSlotPlan() ?: return
val saved = cc.savePos() val saved = cc.savePos()
@ -448,11 +482,11 @@ class Compiler(
val scopeRec = seedScope?.get(name) ?: importManager.rootScope.get(name) val scopeRec = seedScope?.get(name) ?: importManager.rootScope.get(name)
val clsFromScope = scopeRec?.value as? ObjClass val clsFromScope = scopeRec?.value as? ObjClass
val clsFromImports = if (clsFromScope == null) { val clsFromImports = if (clsFromScope == null) {
importedScopes.asReversed().firstNotNullOfOrNull { it.get(name)?.value as? ObjClass } importedModules.asReversed().firstNotNullOfOrNull { it.scope.get(name)?.value as? ObjClass }
} else { } else {
null null
} }
val cls = clsFromScope ?: clsFromImports ?: return null val cls = clsFromScope ?: clsFromImports ?: resolveClassByName(name) ?: return null
val fieldIds = cls.instanceFieldIdMap() val fieldIds = cls.instanceFieldIdMap()
val methodIds = cls.instanceMethodIdMap(includeAbstract = true) val methodIds = cls.instanceMethodIdMap(includeAbstract = true)
val baseNames = cls.directParents.map { it.className } val baseNames = cls.directParents.map { it.className }
@ -836,6 +870,12 @@ class Compiler(
} }
val moduleLoc = if (slotPlanStack.size == 1) lookupSlotLocation(name, includeModule = true) else null val moduleLoc = if (slotPlanStack.size == 1) lookupSlotLocation(name, includeModule = true) else null
if (moduleLoc != null) { if (moduleLoc != null) {
val moduleDeclaredNames = localNamesStack.firstOrNull()
if (moduleDeclaredNames == null || !moduleDeclaredNames.contains(name)) {
resolveImportBinding(name, pos)?.let { resolved ->
registerImportBinding(name, resolved.binding, pos)
}
}
val ref = LocalSlotRef( val ref = LocalSlotRef(
name, name,
moduleLoc.slot, moduleLoc.slot,
@ -854,28 +894,18 @@ class Compiler(
val ids = resolveMemberIds(name, pos, null) val ids = resolveMemberIds(name, pos, null)
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName()) return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName())
} }
if (classCtx != null) { val implicitTypeFromFunc = implicitReceiverTypeForMember(name)
val implicitType = classCtx.name val hasImplicitClassMember = classCtx != null && hasImplicitThisMember(name, classCtx.name)
if (hasImplicitThisMember(name, implicitType)) { if (implicitTypeFromFunc == null && !hasImplicitClassMember) {
resolutionSink?.referenceMember(name, pos, implicitType)
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
val preferredType = if (currentImplicitThisTypeName() == null) null else implicitType
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, preferredType)
}
}
val implicitType = implicitReceiverTypeForMember(name)
if (implicitType != null) {
resolutionSink?.referenceMember(name, pos, implicitType)
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType)
}
if (classCtx != null && classCtx.classScopeMembers.contains(name)) {
resolutionSink?.referenceMember(name, pos, classCtx.name)
return ClassScopeMemberRef(name, pos, classCtx.name)
}
val modulePlan = moduleSlotPlan() val modulePlan = moduleSlotPlan()
val moduleEntry = modulePlan?.slots?.get(name) val moduleEntry = modulePlan?.slots?.get(name)
if (moduleEntry != null) { if (moduleEntry != null) {
val moduleDeclaredNames = localNamesStack.firstOrNull()
if (moduleDeclaredNames == null || !moduleDeclaredNames.contains(name)) {
resolveImportBinding(name, pos)?.let { resolved ->
registerImportBinding(name, resolved.binding, pos)
}
}
val moduleLoc = SlotLocation( val moduleLoc = SlotLocation(
moduleEntry.index, moduleEntry.index,
slotPlanStack.size - 1, slotPlanStack.size - 1,
@ -899,19 +929,40 @@ class Compiler(
resolutionSink?.reference(name, pos) resolutionSink?.reference(name, pos)
return ref return ref
} }
val rootRecord = importManager.rootScope.objects[name] resolveImportBinding(name, pos)?.let { resolved ->
if (rootRecord != null && rootRecord.visibility.isPublic) { val sourceRecord = resolved.record
modulePlan?.let { plan -> if (modulePlan != null && !modulePlan.slots.containsKey(name)) {
declareSlotNameIn(plan, name, rootRecord.isMutable, rootRecord.type == ObjRecord.Type.Delegated) val seedSlotIndex = if (resolved.binding.source is ImportBindingSource.Seed) {
seedScope?.getSlotIndexOf(name)
} else {
null
} }
val rootSlot = lookupSlotLocation(name) if (seedSlotIndex != null) {
if (rootSlot != null) { declareSlotNameAt(
modulePlan,
name,
seedSlotIndex,
sourceRecord.isMutable,
sourceRecord.type == ObjRecord.Type.Delegated
)
} else {
declareSlotNameIn(
modulePlan,
name,
sourceRecord.isMutable,
sourceRecord.type == ObjRecord.Type.Delegated
)
}
}
registerImportBinding(name, resolved.binding, pos)
val slot = lookupSlotLocation(name)
if (slot != null) {
val ref = LocalSlotRef( val ref = LocalSlotRef(
name, name,
rootSlot.slot, slot.slot,
rootSlot.scopeId, slot.scopeId,
rootSlot.isMutable, slot.isMutable,
rootSlot.isDelegated, slot.isDelegated,
pos, pos,
strictSlotRefs strictSlotRefs
) )
@ -919,6 +970,26 @@ class Compiler(
return ref return ref
} }
} }
}
if (classCtx != null) {
val implicitType = classCtx.name
if (hasImplicitThisMember(name, implicitType)) {
resolutionSink?.referenceMember(name, pos, implicitType)
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
val preferredType = if (currentImplicitThisTypeName() == null) null else implicitType
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, preferredType)
}
}
val implicitType = implicitTypeFromFunc
if (implicitType != null) {
resolutionSink?.referenceMember(name, pos, implicitType)
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType)
}
if (classCtx != null && classCtx.classScopeMembers.contains(name)) {
resolutionSink?.referenceMember(name, pos, classCtx.name)
return ClassScopeMemberRef(name, pos, classCtx.name)
}
val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody } val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody }
if (classContext && extensionNames.contains(name)) { if (classContext && extensionNames.contains(name)) {
resolutionSink?.referenceMember(name, pos) resolutionSink?.referenceMember(name, pos)
@ -960,7 +1031,10 @@ class Compiler(
private val seedScope: Scope? = settings.seedScope private val seedScope: Scope? = settings.seedScope
private var resolutionScriptDepth = 0 private var resolutionScriptDepth = 0
private val resolutionPredeclared = mutableSetOf<String>() private val resolutionPredeclared = mutableSetOf<String>()
private val importedScopes = mutableListOf<Scope>() private data class ImportedModule(val scope: ModuleScope, val pos: Pos)
private data class ImportBindingResolution(val binding: ImportBinding, val record: ObjRecord)
private val importedModules = mutableListOf<ImportedModule>()
private val importBindings = mutableMapOf<String, ImportBinding>()
private val enumEntriesByName = mutableMapOf<String, List<String>>() private val enumEntriesByName = mutableMapOf<String, List<String>>()
// --- Doc-comment collection state (for immediate preceding declarations) --- // --- Doc-comment collection state (for immediate preceding declarations) ---
@ -997,6 +1071,118 @@ class Compiler(
} }
} }
private fun seedNameObjClassFromScope(scope: Scope) {
var current: Scope? = scope
while (current != null) {
for ((name, record) in current.objects) {
if (!record.visibility.isPublic) continue
if (nameObjClass.containsKey(name)) continue
when (val value = record.value) {
is ObjClass -> nameObjClass[name] = value
is ObjInstance -> nameObjClass[name] = value.objClass
}
}
current = current.parent
}
}
private fun resolveImportBinding(name: String, pos: Pos): ImportBindingResolution? {
val seedRecord = findSeedScopeRecord(name)?.takeIf { it.visibility.isPublic }
val rootRecord = importManager.rootScope.objects[name]?.takeIf { it.visibility.isPublic }
val moduleMatches = LinkedHashMap<String, Pair<ImportedModule, ObjRecord>>()
for (module in importedModules.asReversed()) {
val found = LinkedHashMap<String, Pair<ModuleScope, ObjRecord>>()
collectModuleRecordMatches(module.scope, name, mutableSetOf(), found)
for ((pkg, pair) in found) {
moduleMatches.putIfAbsent(pkg, ImportedModule(pair.first, module.pos) to pair.second)
}
}
if (seedRecord != null) {
val value = seedRecord.value
if (!nameObjClass.containsKey(name)) {
when (value) {
is ObjClass -> nameObjClass[name] = value
is ObjInstance -> nameObjClass[name] = value.objClass
}
}
return ImportBindingResolution(ImportBinding(name, ImportBindingSource.Seed), seedRecord)
}
if (rootRecord != null) {
val value = rootRecord.value
if (!nameObjClass.containsKey(name)) {
when (value) {
is ObjClass -> nameObjClass[name] = value
is ObjInstance -> nameObjClass[name] = value.objClass
}
}
return ImportBindingResolution(ImportBinding(name, ImportBindingSource.Root), rootRecord)
}
if (moduleMatches.isEmpty()) return null
if (moduleMatches.size > 1) {
val moduleNames = moduleMatches.keys.toList()
throw ScriptError(pos, "symbol $name is ambiguous between imports: ${moduleNames.joinToString(", ")}")
}
val (module, record) = moduleMatches.values.first()
val binding = ImportBinding(name, ImportBindingSource.Module(module.scope.packageName, module.pos))
val value = record.value
if (!nameObjClass.containsKey(name)) {
when (value) {
is ObjClass -> nameObjClass[name] = value
is ObjInstance -> nameObjClass[name] = value.objClass
}
}
return ImportBindingResolution(binding, record)
}
private fun collectModuleRecordMatches(
scope: ModuleScope,
name: String,
visited: MutableSet<String>,
out: MutableMap<String, Pair<ModuleScope, ObjRecord>>
) {
if (!visited.add(scope.packageName)) return
val record = scope.objects[name]
if (record != null && record.visibility.isPublic) {
out.putIfAbsent(scope.packageName, scope to record)
}
for (child in scope.importedModules) {
collectModuleRecordMatches(child, name, visited, out)
}
}
private fun registerImportBinding(name: String, binding: ImportBinding, pos: Pos) {
val existing = importBindings[name] ?: run {
importBindings[name] = binding
return
}
if (!sameImportBinding(existing, binding)) {
throw ScriptError(pos, "symbol $name resolves to multiple imports")
}
}
private fun sameImportBinding(left: ImportBinding, right: ImportBinding): Boolean {
if (left.symbol != right.symbol) return false
val leftSrc = left.source
val rightSrc = right.source
return when (leftSrc) {
is ImportBindingSource.Module -> {
rightSrc is ImportBindingSource.Module && leftSrc.name == rightSrc.name
}
ImportBindingSource.Root -> rightSrc is ImportBindingSource.Root
ImportBindingSource.Seed -> rightSrc is ImportBindingSource.Seed
}
}
private fun findSeedScopeRecord(name: String): ObjRecord? {
var current = seedScope
var hops = 0
while (current != null && hops++ < 1024) {
current.objects[name]?.let { return it }
current = current.parent
}
return null
}
private fun shouldSeedDefaultStdlib(): Boolean { private fun shouldSeedDefaultStdlib(): Boolean {
if (seedScope != null) return false if (seedScope != null) return false
if (importManager !== Script.defaultImportManager) return false if (importManager !== Script.defaultImportManager) return false
@ -1216,15 +1402,25 @@ class Compiler(
val needsSlotPlan = slotPlanStack.isEmpty() val needsSlotPlan = slotPlanStack.isEmpty()
if (needsSlotPlan) { if (needsSlotPlan) {
slotPlanStack.add(SlotPlan(mutableMapOf(), 0, nextScopeId++)) slotPlanStack.add(SlotPlan(mutableMapOf(), 0, nextScopeId++))
declareSlotNameIn(slotPlanStack.last(), "__PACKAGE__", isMutable = false, isDelegated = false) seedScope?.let { scope ->
declareSlotNameIn(slotPlanStack.last(), "$~", isMutable = true, isDelegated = false) if (scope !is ModuleScope) {
seedScope?.let { seedSlotPlanFromScope(it, includeParents = true) } seedSlotPlanFromSeedScope(scope)
seedSlotPlanFromScope(importManager.rootScope) }
}
val plan = slotPlanStack.last()
if (!plan.slots.containsKey("__PACKAGE__")) {
declareSlotNameIn(plan, "__PACKAGE__", isMutable = false, isDelegated = false)
}
if (!plan.slots.containsKey("$~")) {
declareSlotNameIn(plan, "$~", isMutable = true, isDelegated = false)
}
seedScope?.let { seedNameObjClassFromScope(it) }
seedNameObjClassFromScope(importManager.rootScope)
if (shouldSeedDefaultStdlib()) { if (shouldSeedDefaultStdlib()) {
val stdlib = importManager.prepareImport(start, "lyng.stdlib", null) val stdlib = importManager.prepareImport(start, "lyng.stdlib", null)
seedResolutionFromScope(stdlib, start) seedResolutionFromScope(stdlib, start)
seedSlotPlanFromScope(stdlib) seedNameObjClassFromScope(stdlib)
importedScopes.add(stdlib) importedModules.add(ImportedModule(stdlib, start))
} }
predeclareTopLevelSymbols() predeclareTopLevelSymbols()
} }
@ -1293,16 +1489,8 @@ class Compiler(
} }
} }
val module = importManager.prepareImport(pos, name, null) val module = importManager.prepareImport(pos, name, null)
importedScopes.add(module) importedModules.add(ImportedModule(module, pos))
seedResolutionFromScope(module, pos) seedResolutionFromScope(module, pos)
seedSlotPlanFromScope(module)
statements += object : Statement() {
override val pos: Pos = pos
override suspend fun execute(scope: Scope): Obj {
module.importInto(scope, null)
return ObjVoid
}
}
continue continue
} }
} }
@ -1336,7 +1524,8 @@ class Compiler(
statements.isNotEmpty() && statements.isNotEmpty() &&
codeContexts.lastOrNull() is CodeContext.Module && codeContexts.lastOrNull() is CodeContext.Module &&
resolutionScriptDepth == 1 && resolutionScriptDepth == 1 &&
statements.none { containsUnsupportedForBytecode(it) } statements.none { containsUnsupportedForBytecode(it) } &&
statements.none { containsDelegatedRefs(it) }
val finalStatements = if (wrapScriptBytecode) { val finalStatements = if (wrapScriptBytecode) {
val unwrapped = statements.map { unwrapBytecodeDeep(it) } val unwrapped = statements.map { unwrapBytecodeDeep(it) }
val block = InlineBlockStatement(unwrapped, start) val block = InlineBlockStatement(unwrapped, start)
@ -1354,7 +1543,8 @@ class Compiler(
} else { } else {
statements statements
} }
Script(start, finalStatements, modulePlan) val moduleRefs = importedModules.map { ImportBindingSource.Module(it.scope.packageName, it.pos) }
Script(start, finalStatements, modulePlan, importBindings.toMap(), moduleRefs)
}.also { }.also {
// Best-effort script end notification (use current position) // Best-effort script end notification (use current position)
miniSink?.onScriptEnd( miniSink?.onScriptEnd(
@ -1415,8 +1605,23 @@ class Compiler(
val candidates = mutableListOf(typeName) val candidates = mutableListOf(typeName)
cls?.mro?.forEach { candidates.add(it.className) } cls?.mro?.forEach { candidates.add(it.className) }
for (baseName in candidates) { for (baseName in candidates) {
val wrapperName = extensionCallableName(baseName, memberName) val wrapperNames = listOf(
if (seedScope?.get(wrapperName) != null || importManager.rootScope.get(wrapperName) != null) { extensionCallableName(baseName, memberName),
extensionPropertyGetterName(baseName, memberName),
extensionPropertySetterName(baseName, memberName)
)
for (wrapperName in wrapperNames) {
val resolved = resolveImportBinding(wrapperName, Pos.builtIn) ?: continue
val plan = moduleSlotPlan()
if (plan != null && !plan.slots.containsKey(wrapperName)) {
declareSlotNameIn(
plan,
wrapperName,
resolved.record.isMutable,
resolved.record.type == ObjRecord.Type.Delegated
)
}
registerImportBinding(wrapperName, resolved.binding, Pos.builtIn)
return true return true
} }
} }
@ -1578,8 +1783,8 @@ class Compiler(
} }
addScope(seedScope) addScope(seedScope)
addScope(importManager.rootScope) addScope(importManager.rootScope)
for (scope in importedScopes) { for (module in importedModules) {
addScope(scope) addScope(module.scope)
} }
for (name in compileClassInfos.keys) { for (name in compileClassInfos.keys) {
val cls = resolveClassByName(name) ?: continue val cls = resolveClassByName(name) ?: continue
@ -1730,8 +1935,11 @@ class Compiler(
containsUnsupportedRef(ref.condition) || containsUnsupportedRef(ref.ifTrue) || containsUnsupportedRef(ref.ifFalse) containsUnsupportedRef(ref.condition) || containsUnsupportedRef(ref.ifTrue) || containsUnsupportedRef(ref.ifFalse)
is ElvisRef -> containsUnsupportedRef(ref.left) || containsUnsupportedRef(ref.right) is ElvisRef -> containsUnsupportedRef(ref.left) || containsUnsupportedRef(ref.right)
is FieldRef -> { is FieldRef -> {
val receiverClass = resolveReceiverClassForMember(ref.target) val receiverClass = resolveReceiverClassForMember(ref.target) ?: return true
if (receiverClass == ObjDynamic.type) return true if (receiverClass == ObjDynamic.type) return true
val hasMember = receiverClass.instanceFieldIdMap()[ref.name] != null ||
receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] != null
if (!hasMember && !hasExtensionFor(receiverClass.className, ref.name)) return true
containsUnsupportedRef(ref.target) containsUnsupportedRef(ref.target)
} }
is IndexRef -> containsUnsupportedRef(ref.targetRef) || containsUnsupportedRef(ref.indexRef) is IndexRef -> containsUnsupportedRef(ref.targetRef) || containsUnsupportedRef(ref.indexRef)
@ -1747,12 +1955,24 @@ class Compiler(
is net.sergeych.lyng.obj.MapLiteralEntry.Spread -> containsUnsupportedRef(it.ref) is net.sergeych.lyng.obj.MapLiteralEntry.Spread -> containsUnsupportedRef(it.ref)
} }
} }
is CallRef -> containsUnsupportedRef(ref.target) || ref.args.any { containsUnsupportedForBytecode(it.value) } is CallRef -> {
val targetName = when (val target = ref.target) {
is LocalVarRef -> target.name
is LocalSlotRef -> target.name
else -> null
}
if (targetName == "delay") return true
containsUnsupportedRef(ref.target) || ref.args.any { containsUnsupportedForBytecode(it.value) }
}
is MethodCallRef -> { is MethodCallRef -> {
val receiverClass = resolveReceiverClassForMember(ref.receiver) if (ref.name == "delay") return true
val receiverClass = resolveReceiverClassForMember(ref.receiver) ?: return true
if (receiverClass == ObjDynamic.type) return true if (receiverClass == ObjDynamic.type) return true
val hasMember = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] != null
if (!hasMember && !hasExtensionFor(receiverClass.className, ref.name)) return true
containsUnsupportedRef(ref.receiver) || ref.args.any { containsUnsupportedForBytecode(it.value) } containsUnsupportedRef(ref.receiver) || ref.args.any { containsUnsupportedForBytecode(it.value) }
} }
is ImplicitThisMethodCallRef -> true
is QualifiedThisMethodSlotCallRef -> true is QualifiedThisMethodSlotCallRef -> true
is QualifiedThisFieldSlotRef -> true is QualifiedThisFieldSlotRef -> true
is ClassScopeMemberRef -> true is ClassScopeMemberRef -> true
@ -1766,7 +1986,7 @@ class Compiler(
is ExpressionStatement -> containsDelegatedRefs(target.ref) is ExpressionStatement -> containsDelegatedRefs(target.ref)
is BlockStatement -> target.statements().any { containsDelegatedRefs(it) } is BlockStatement -> target.statements().any { containsDelegatedRefs(it) }
is VarDeclStatement -> target.initializer?.let { containsDelegatedRefs(it) } ?: false is VarDeclStatement -> target.initializer?.let { containsDelegatedRefs(it) } ?: false
is DelegatedVarDeclStatement -> containsDelegatedRefs(target.initializer) is DelegatedVarDeclStatement -> true
is DestructuringVarDeclStatement -> containsDelegatedRefs(target.initializer) is DestructuringVarDeclStatement -> containsDelegatedRefs(target.initializer)
is IfStatement -> { is IfStatement -> {
containsDelegatedRefs(target.condition) || containsDelegatedRefs(target.condition) ||
@ -1812,6 +2032,18 @@ class Compiler(
private fun containsDelegatedRefs(ref: ObjRef): Boolean { private fun containsDelegatedRefs(ref: ObjRef): Boolean {
return when (ref) { return when (ref) {
is LocalSlotRef -> ref.isDelegated is LocalSlotRef -> ref.isDelegated
is ImplicitThisMemberRef -> {
val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName()
val targetClass = typeName?.let { resolveClassByName(it) }
val member = targetClass?.findFirstConcreteMember(ref.name)
member?.type == ObjRecord.Type.Delegated
}
is ImplicitThisMethodCallRef -> {
val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName()
val targetClass = typeName?.let { resolveClassByName(it) }
val member = targetClass?.findFirstConcreteMember(ref.methodName())
member?.type == ObjRecord.Type.Delegated
}
is BinaryOpRef -> containsDelegatedRefs(ref.left) || containsDelegatedRefs(ref.right) is BinaryOpRef -> containsDelegatedRefs(ref.left) || containsDelegatedRefs(ref.right)
is UnaryOpRef -> containsDelegatedRefs(ref.a) is UnaryOpRef -> containsDelegatedRefs(ref.a)
is CastRef -> containsDelegatedRefs(ref.castValueRef()) || containsDelegatedRefs(ref.castTypeRef()) is CastRef -> containsDelegatedRefs(ref.castValueRef()) || containsDelegatedRefs(ref.castTypeRef())
@ -1826,7 +2058,14 @@ class Compiler(
is ConditionalRef -> is ConditionalRef ->
containsDelegatedRefs(ref.condition) || containsDelegatedRefs(ref.ifTrue) || containsDelegatedRefs(ref.ifFalse) containsDelegatedRefs(ref.condition) || containsDelegatedRefs(ref.ifTrue) || containsDelegatedRefs(ref.ifFalse)
is ElvisRef -> containsDelegatedRefs(ref.left) || containsDelegatedRefs(ref.right) is ElvisRef -> containsDelegatedRefs(ref.left) || containsDelegatedRefs(ref.right)
is FieldRef -> containsDelegatedRefs(ref.target) is FieldRef -> {
val receiverClass = resolveReceiverClassForMember(ref.target)
if (receiverClass != null) {
val member = receiverClass.findFirstConcreteMember(ref.name)
if (member?.type == ObjRecord.Type.Delegated) return true
}
containsDelegatedRefs(ref.target)
}
is IndexRef -> containsDelegatedRefs(ref.targetRef) || containsDelegatedRefs(ref.indexRef) is IndexRef -> containsDelegatedRefs(ref.targetRef) || containsDelegatedRefs(ref.indexRef)
is ListLiteralRef -> ref.entries().any { is ListLiteralRef -> ref.entries().any {
when (it) { when (it) {
@ -1841,7 +2080,14 @@ class Compiler(
} }
} }
is CallRef -> containsDelegatedRefs(ref.target) || ref.args.any { containsDelegatedRefs(it.value) } is CallRef -> containsDelegatedRefs(ref.target) || ref.args.any { containsDelegatedRefs(it.value) }
is MethodCallRef -> containsDelegatedRefs(ref.receiver) || ref.args.any { containsDelegatedRefs(it.value) } is MethodCallRef -> {
val receiverClass = resolveReceiverClassForMember(ref.receiver)
if (receiverClass != null) {
val member = receiverClass.findFirstConcreteMember(ref.name)
if (member?.type == ObjRecord.Type.Delegated) return true
}
containsDelegatedRefs(ref.receiver) || ref.args.any { containsDelegatedRefs(it.value) }
}
is StatementRef -> containsDelegatedRefs(ref.statement) is StatementRef -> containsDelegatedRefs(ref.statement)
else -> false else -> false
} }
@ -3939,6 +4185,15 @@ class Compiler(
if (targetClass == ObjInstant.type && (name == "distantFuture" || name == "distantPast")) { if (targetClass == ObjInstant.type && (name == "distantFuture" || name == "distantPast")) {
return ObjInstant.type return ObjInstant.type
} }
if (targetClass == ObjInstant.type && name in listOf(
"truncateToMinute",
"truncateToSecond",
"truncateToMillisecond",
"truncateToMicrosecond"
)
) {
return ObjInstant.type
}
if (targetClass == ObjString.type && name == "re") { if (targetClass == ObjString.type && name == "re") {
return ObjRegex.type return ObjRegex.type
} }
@ -4020,6 +4275,7 @@ class Compiler(
if (isAllowedObjectMember(memberName)) return if (isAllowedObjectMember(memberName)) return
throw ScriptError(pos, "member access requires compile-time receiver type: $memberName") throw ScriptError(pos, "member access requires compile-time receiver type: $memberName")
} }
registerExtensionWrapperBindings(receiverClass, memberName, pos)
if (receiverClass == Obj.rootObjectType) { if (receiverClass == Obj.rootObjectType) {
val allowed = isAllowedObjectMember(memberName) val allowed = isAllowedObjectMember(memberName)
if (!allowed && !hasExtensionFor(receiverClass.className, memberName)) { if (!allowed && !hasExtensionFor(receiverClass.className, memberName)) {
@ -4028,6 +4284,29 @@ class Compiler(
} }
} }
private fun registerExtensionWrapperBindings(receiverClass: ObjClass, memberName: String, pos: Pos) {
for (cls in receiverClass.mro) {
val wrapperNames = listOf(
extensionCallableName(cls.className, memberName),
extensionPropertyGetterName(cls.className, memberName),
extensionPropertySetterName(cls.className, memberName)
)
for (wrapperName in wrapperNames) {
val resolved = resolveImportBinding(wrapperName, pos) ?: continue
val plan = moduleSlotPlan()
if (plan != null && !plan.slots.containsKey(wrapperName)) {
declareSlotNameIn(
plan,
wrapperName,
resolved.record.isMutable,
resolved.record.type == ObjRecord.Type.Delegated
)
}
registerImportBinding(wrapperName, resolved.binding, pos)
}
}
}
private fun isAllowedObjectMember(memberName: String): Boolean { private fun isAllowedObjectMember(memberName: String): Boolean {
return when (memberName) { return when (memberName) {
"toString", "toString",
@ -4489,7 +4768,7 @@ class Compiler(
} }
} }
val implicitThisTypeName = currentImplicitThisTypeName() val implicitThisTypeName = currentImplicitThisTypeName()
return when (left) { val result = when (left) {
is ImplicitThisMemberRef -> is ImplicitThisMemberRef ->
if (left.methodId == null && left.fieldId != null) { if (left.methodId == null && left.fieldId != null) {
CallRef(left, args, detectedBlockArgument, isOptional) CallRef(left, args, detectedBlockArgument, isOptional)
@ -4550,6 +4829,7 @@ class Compiler(
} }
else -> CallRef(left, args, detectedBlockArgument, isOptional) else -> CallRef(left, args, detectedBlockArgument, isOptional)
} }
return result
} }
private fun inferReceiverTypeFromArgs(args: List<ParsedArgument>): String? { private fun inferReceiverTypeFromArgs(args: List<ParsedArgument>): String? {
@ -5930,6 +6210,14 @@ class Compiler(
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
// the main statement should create custom ObjClass instance with field // the main statement should create custom ObjClass instance with field
// accessors, constructor registration, etc. // accessors, constructor registration, etc.
if (isExtern) {
val rec = scope[className]
val existing = rec?.value as? ObjClass
val resolved = existing ?: resolveClassByName(className)
val stub = resolved ?: ObjInstanceClass(className).apply { this.isAbstract = true }
scope.addItem(declaredName, false, stub)
return stub
}
// Resolve parent classes by name at execution time // Resolve parent classes by name at execution time
val parentClasses = baseSpecs.map { baseSpec -> val parentClasses = baseSpecs.map { baseSpec ->
val rec = scope[baseSpec.name] val rec = scope[baseSpec.name]
@ -6566,7 +6854,11 @@ class Compiler(
} }
} }
val fnStatements = rawFnStatements?.let { stmt -> val fnStatements = rawFnStatements?.let { stmt ->
if (useBytecodeStatements && !containsUnsupportedForBytecode(stmt)) { if (useBytecodeStatements &&
parentContext !is CodeContext.ClassBody &&
!containsUnsupportedForBytecode(stmt) &&
!containsDelegatedRefs(stmt)
) {
val paramKnownClasses = mutableMapOf<String, ObjClass>() val paramKnownClasses = mutableMapOf<String, ObjClass>()
for (param in argsDeclaration.params) { for (param in argsDeclaration.params) {
val cls = resolveTypeDeclObjClass(param.type) ?: continue val cls = resolveTypeDeclObjClass(param.type) ?: continue
@ -6630,6 +6922,19 @@ class Compiler(
val fnCreateStatement = object : Statement() { val fnCreateStatement = object : Statement() {
override val pos: Pos = start override val pos: Pos = start
override suspend fun execute(context: Scope): Obj { override suspend fun execute(context: Scope): Obj {
if (actualExtern && extTypeName == null && parentContext !is CodeContext.ClassBody) {
val existing = context.get(name)
if (existing != null) {
context.addItem(
name,
false,
existing.value,
visibility,
callSignature = existing.callSignature
)
return existing.value
}
}
if (isDelegated) { if (isDelegated) {
val accessType = ObjString("Callable") val accessType = ObjString("Callable")
val initValue = delegateExpression!!.execute(context) val initValue = delegateExpression!!.execute(context)
@ -6758,7 +7063,9 @@ class Compiler(
) )
execScope.currentClassCtx = cls execScope.currentClassCtx = cls
compiledFnBody.execute(execScope) compiledFnBody.execute(execScope)
} ?: compiledFnBody.execute(thisObj.autoInstanceScope(this)) } ?: run {
compiledFnBody.execute(thisObj.autoInstanceScope(this))
}
} finally { } finally {
this.currentClassCtx = savedCtx this.currentClassCtx = savedCtx
} }
@ -7096,8 +7403,8 @@ class Compiler(
private fun resolveClassByName(name: String): ObjClass? { private fun resolveClassByName(name: String): ObjClass? {
val rec = seedScope?.get(name) ?: importManager.rootScope.get(name) val rec = seedScope?.get(name) ?: importManager.rootScope.get(name)
(rec?.value as? ObjClass)?.let { return it } (rec?.value as? ObjClass)?.let { return it }
for (scope in importedScopes.asReversed()) { for (module in importedModules.asReversed()) {
val imported = scope.get(name) val imported = module.scope.get(name)
(imported?.value as? ObjClass)?.let { return it } (imported?.value as? ObjClass)?.let { return it }
} }
val info = compileClassInfos[name] ?: return null val info = compileClassInfos[name] ?: return null

View File

@ -33,6 +33,8 @@ class ModuleScope(
constructor(importProvider: ImportProvider, source: Source) : this(importProvider, source.startPos, source.fileName) constructor(importProvider: ImportProvider, source: Source) : this(importProvider, source.startPos, source.fileName)
internal var importedModules: List<ModuleScope> = emptyList()
/** /**
* Import symbols into the scope. It _is called_ after the module is imported by [ImportProvider.prepareImport] * Import symbols into the scope. It _is called_ after the module is imported by [ImportProvider.prepareImport]
* which checks symbol availability and accessibility prior to execution. * which checks symbol availability and accessibility prior to execution.
@ -92,4 +94,3 @@ class ModuleScope(
super.get(name) super.get(name)
} }
} }

View File

@ -463,6 +463,24 @@ open class Scope(
} }
} }
internal fun applySlotPlanReset(plan: Map<String, Int>, records: Map<String, ObjRecord>) {
if (plan.isEmpty()) return
slots.clear()
nameToSlot.clear()
val maxIndex = plan.values.maxOrNull() ?: return
val targetSize = maxIndex + 1
repeat(targetSize) {
slots.add(ObjRecord(ObjUnset, isMutable = true))
}
for ((name, idx) in plan) {
nameToSlot[name] = idx
val record = records[name]
if (record != null && record.value !== ObjUnset) {
slots[idx] = record
}
}
}
fun applySlotPlanWithSnapshot(plan: Map<String, Int>): Map<String, Int?> { fun applySlotPlanWithSnapshot(plan: Map<String, Int>): Map<String, Int?> {
if (plan.isEmpty()) return emptyMap() if (plan.isEmpty()) return emptyMap()
val maxIndex = plan.values.maxOrNull() ?: return emptyMap() val maxIndex = plan.values.maxOrNull() ?: return emptyMap()

View File

@ -34,13 +34,50 @@ class Script(
private val statements: List<Statement> = emptyList(), private val statements: List<Statement> = emptyList(),
private val moduleSlotPlan: Map<String, Int> = emptyMap(), private val moduleSlotPlan: Map<String, Int> = emptyMap(),
private val importBindings: Map<String, ImportBinding> = emptyMap(), private val importBindings: Map<String, ImportBinding> = emptyMap(),
private val importedModules: List<ImportBindingSource.Module> = emptyList(),
// private val catchReturn: Boolean = false, // private val catchReturn: Boolean = false,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
if (moduleSlotPlan.isNotEmpty()) { val isModuleScope = scope is ModuleScope
scope.applySlotPlan(moduleSlotPlan) val shouldSeedModule = isModuleScope || scope.thisObj === ObjVoid
seedModuleSlots(scope) val moduleTarget = scope
if (moduleSlotPlan.isNotEmpty() && shouldSeedModule) {
val hasPlanMapping = moduleSlotPlan.keys.any { moduleTarget.getSlotIndexOf(it) != null }
val needsReset = moduleTarget is ModuleScope ||
moduleTarget.slotCount() == 0 ||
moduleTarget.hasSlotPlanConflict(moduleSlotPlan) ||
(!hasPlanMapping && moduleTarget.slotCount() > 0)
if (needsReset) {
val preserved = LinkedHashMap<String, ObjRecord>()
for (name in moduleSlotPlan.keys) {
moduleTarget.getLocalRecordDirect(name)?.let { preserved[name] = it }
}
moduleTarget.applySlotPlanReset(moduleSlotPlan, preserved)
for (name in moduleSlotPlan.keys) {
if (preserved.containsKey(name)) continue
val inherited = findSeedRecord(moduleTarget.parent, name)
if (inherited != null && inherited.value !== ObjUnset) {
moduleTarget.updateSlotFor(name, inherited)
}
}
} else {
moduleTarget.applySlotPlan(moduleSlotPlan)
for (name in moduleSlotPlan.keys) {
val local = moduleTarget.getLocalRecordDirect(name)
if (local != null && local.value !== ObjUnset) {
moduleTarget.updateSlotFor(name, local)
continue
}
val inherited = findSeedRecord(moduleTarget.parent, name)
if (inherited != null && inherited.value !== ObjUnset) {
moduleTarget.updateSlotFor(name, inherited)
}
}
}
}
if (shouldSeedModule) {
seedModuleSlots(moduleTarget)
} }
var lastResult: Obj = ObjVoid var lastResult: Obj = ObjVoid
for (s in statements) { for (s in statements) {
@ -50,37 +87,24 @@ class Script(
} }
private suspend fun seedModuleSlots(scope: Scope) { private suspend fun seedModuleSlots(scope: Scope) {
if (importBindings.isNotEmpty()) { if (importBindings.isEmpty() && importedModules.isEmpty()) return
seedImportBindings(scope) seedImportBindings(scope)
return
}
val parent = scope.parent ?: return
for (name in moduleSlotPlan.keys) {
if (scope.objects.containsKey(name)) {
scope.updateSlotFor(name, scope.objects[name]!!)
continue
}
val seed = findSeedRecord(parent, name)
if (seed != null) {
if (name == "Exception" && seed.value !is ObjClass) {
scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false))
} else {
scope.updateSlotFor(name, seed)
}
continue
}
if (name == "Exception") {
scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false))
}
}
} }
private suspend fun seedImportBindings(scope: Scope) { private suspend fun seedImportBindings(scope: Scope) {
val provider = scope.currentImportProvider val provider = scope.currentImportProvider
val importedModules = LinkedHashSet<ModuleScope>()
for (moduleRef in this.importedModules) {
importedModules.add(provider.prepareImport(moduleRef.pos, moduleRef.name, null))
}
if (scope is ModuleScope) {
scope.importedModules = importedModules.toList()
}
for ((name, binding) in importBindings) { for ((name, binding) in importBindings) {
val record = when (val source = binding.source) { val record = when (val source = binding.source) {
is ImportBindingSource.Module -> { is ImportBindingSource.Module -> {
val module = provider.prepareImport(source.pos, source.name, null) val module = provider.prepareImport(source.pos, source.name, null)
importedModules.add(module)
module.objects[binding.symbol]?.takeIf { it.visibility.isPublic } module.objects[binding.symbol]?.takeIf { it.visibility.isPublic }
?: scope.raiseSymbolNotFound("symbol ${source.name}.${binding.symbol} not found") ?: scope.raiseSymbolNotFound("symbol ${source.name}.${binding.symbol} not found")
} }
@ -99,6 +123,15 @@ class Script(
scope.updateSlotFor(name, record) scope.updateSlotFor(name, record)
} }
} }
for (module in importedModules) {
for ((cls, map) in module.extensions) {
for ((symbol, record) in map) {
if (record.visibility.isPublic) {
scope.addExtension(cls, symbol, record)
}
}
}
}
} }
private fun findSeedRecord(scope: Scope?, name: String): ObjRecord? { private fun findSeedRecord(scope: Scope?, name: String): ObjRecord? {
@ -116,6 +149,15 @@ class Script(
return null return null
} }
private fun resolveModuleScope(scope: Scope): ModuleScope? {
var current: Scope? = scope
while (current != null) {
if (current is ModuleScope) return current
current = current.parent
}
return null
}
internal fun debugStatements(): List<Statement> = statements internal fun debugStatements(): List<Statement> = statements
suspend fun execute() = execute( suspend fun execute() = execute(
@ -460,6 +502,7 @@ class Script(
// interfaces // interfaces
addConst("Iterable", ObjIterable) addConst("Iterable", ObjIterable)
addConst("Collection", ObjCollection) addConst("Collection", ObjCollection)
addConst("Iterator", ObjIterator)
addConst("Array", ObjArray) addConst("Array", ObjArray)
addConst("RingBuffer", ObjRingBuffer.type) addConst("RingBuffer", ObjRingBuffer.type)
addConst("Class", ObjClassType) addConst("Class", ObjClassType)

View File

@ -211,6 +211,12 @@ class BytecodeCompiler(
private fun allocSlot(): Int = nextSlot++ private fun allocSlot(): Int = nextSlot++
private fun encodeMemberId(receiverClass: ObjClass, id: Int?): Int? {
if (id == null) return null
if (receiverClass == ObjClassType) return -(id + 2)
return id
}
private fun compileRef(ref: ObjRef): CompiledValue? { private fun compileRef(ref: ObjRef): CompiledValue? {
return when (ref) { return when (ref) {
is ConstRef -> compileConst(ref.constValue) is ConstRef -> compileConst(ref.constValue)
@ -1620,13 +1626,13 @@ class BytecodeCompiler(
Pos.builtIn Pos.builtIn
) )
val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null
val fieldId = receiverClass.instanceFieldIdMap()[target.name] ?: -1 val fieldId = receiverClass.instanceFieldIdMap()[target.name]
val methodId = if (fieldId < 0) { val methodId = if (fieldId == null) {
receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name] ?: -1 receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name]
} else { } else {
-1 null
} }
if (fieldId < 0 && methodId < 0) { if (fieldId == null && methodId == null) {
val extSlot = resolveExtensionSetterSlot(receiverClass, target.name) val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
?: throw BytecodeCompileException( ?: throw BytecodeCompileException(
"Unknown member ${target.name} on ${receiverClass.className}", "Unknown member ${target.name} on ${receiverClass.className}",
@ -1660,8 +1666,10 @@ class BytecodeCompiler(
} }
return value return value
} }
val encodedFieldId = encodeMemberId(receiverClass, fieldId) ?: -1
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: -1
if (!target.isOptional) { if (!target.isOptional) {
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, value.slot) builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, encodedFieldId, encodedMethodId, value.slot)
} else { } else {
val nullSlot = allocSlot() val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot) builder.emit(Opcode.CONST_NULL, nullSlot)
@ -1672,7 +1680,7 @@ class BytecodeCompiler(
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel)) listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
) )
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, value.slot) builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, encodedFieldId, encodedMethodId, value.slot)
builder.mark(endLabel) builder.mark(endLabel)
} }
return value return value
@ -2048,11 +2056,13 @@ class BytecodeCompiler(
} }
val fieldId = receiverClass.instanceFieldIdMap()[ref.name] val fieldId = receiverClass.instanceFieldIdMap()[ref.name]
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
val encodedFieldId = encodeMemberId(receiverClass, fieldId)
val encodedMethodId = encodeMemberId(receiverClass, methodId)
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
val dst = allocSlot() val dst = allocSlot()
if (fieldId != null || methodId != null) { if (fieldId != null || methodId != null) {
if (!ref.isOptional) { if (!ref.isOptional) {
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, dst) builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, encodedFieldId ?: -1, encodedMethodId ?: -1, dst)
} else { } else {
val nullSlot = allocSlot() val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot) builder.emit(Opcode.CONST_NULL, nullSlot)
@ -2064,7 +2074,7 @@ class BytecodeCompiler(
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel)) listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
) )
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, dst) builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, encodedFieldId ?: -1, encodedMethodId ?: -1, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel) builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, dst) builder.emit(Opcode.CONST_NULL, dst)
@ -2856,11 +2866,12 @@ class BytecodeCompiler(
val dst = allocSlot() val dst = allocSlot()
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
if (methodId != null) { if (methodId != null) {
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId
if (!ref.isOptional) { if (!ref.isOptional) {
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos) setPos(callPos)
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst) builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, encodedMethodId, args.base, encodedCount, dst)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
val nullSlot = allocSlot() val nullSlot = allocSlot()
@ -2876,7 +2887,7 @@ class BytecodeCompiler(
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos) setPos(callPos)
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst) builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, encodedMethodId, args.base, encodedCount, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel) builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, dst) builder.emit(Opcode.CONST_NULL, dst)
@ -4659,43 +4670,17 @@ class BytecodeCompiler(
?: resolveReceiverClass(ref.castValueRef()) ?: resolveReceiverClass(ref.castValueRef())
is FieldRef -> { is FieldRef -> {
val targetClass = resolveReceiverClass(ref.target) ?: return null val targetClass = resolveReceiverClass(ref.target) ?: return null
if (targetClass == ObjString.type && ref.name == "re") { inferFieldReturnClass(targetClass, ref.name)
ObjRegex.type
} else {
null
}
} }
is MethodCallRef -> { is MethodCallRef -> {
val targetClass = resolveReceiverClass(ref.receiver) ?: return null val targetClass = resolveReceiverClass(ref.receiver) ?: return null
if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) { if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) {
ObjRegex.type ObjRegex.type
} else { } else {
when (ref.name) { inferMethodCallReturnClass(ref.name)
"map",
"mapNotNull",
"filter",
"filterNotNull",
"drop",
"take",
"flatMap",
"flatten",
"sorted",
"sortedBy",
"sortedWith",
"reversed",
"toList",
"shuffle",
"shuffled" -> ObjList.type
"dropLast" -> ObjFlow.type
"takeLast" -> ObjRingBuffer.type
"count" -> ObjInt.type
"toSet" -> ObjSet.type
"toMap" -> ObjMap.type
"joinToString" -> ObjString.type
else -> null
}
} }
} }
is CallRef -> inferCallReturnClass(ref)
else -> null else -> null
} }
} }
@ -4742,39 +4727,17 @@ class BytecodeCompiler(
?: resolveReceiverClassForScopeCollection(ref.castValueRef()) ?: resolveReceiverClassForScopeCollection(ref.castValueRef())
is FieldRef -> { is FieldRef -> {
val targetClass = resolveReceiverClassForScopeCollection(ref.target) ?: return null val targetClass = resolveReceiverClassForScopeCollection(ref.target) ?: return null
if (targetClass == ObjString.type && ref.name == "re") ObjRegex.type else null inferFieldReturnClass(targetClass, ref.name)
} }
is MethodCallRef -> { is MethodCallRef -> {
val targetClass = resolveReceiverClassForScopeCollection(ref.receiver) ?: return null val targetClass = resolveReceiverClassForScopeCollection(ref.receiver) ?: return null
if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) { if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) {
ObjRegex.type ObjRegex.type
} else { } else {
when (ref.name) { inferMethodCallReturnClass(ref.name)
"map",
"mapNotNull",
"filter",
"filterNotNull",
"drop",
"take",
"flatMap",
"flatten",
"sorted",
"sortedBy",
"sortedWith",
"reversed",
"toList",
"shuffle",
"shuffled" -> ObjList.type
"dropLast" -> ObjFlow.type
"takeLast" -> ObjRingBuffer.type
"count" -> ObjInt.type
"toSet" -> ObjSet.type
"toMap" -> ObjMap.type
"joinToString" -> ObjString.type
else -> null
}
} }
} }
is CallRef -> inferCallReturnClass(ref)
else -> null else -> null
} }
} }
@ -4813,12 +4776,154 @@ class BytecodeCompiler(
"Regex" -> ObjRegex.type "Regex" -> ObjRegex.type
"RegexMatch" -> ObjRegexMatch.type "RegexMatch" -> ObjRegexMatch.type
"MapEntry" -> ObjMapEntry.type "MapEntry" -> ObjMapEntry.type
"Instant" -> ObjInstant.type
"DateTime" -> ObjDateTime.type
"Duration" -> ObjDuration.type
"Exception" -> ObjException.Root "Exception" -> ObjException.Root
"Class" -> ObjClassType
"Callable" -> Statement.type "Callable" -> Statement.type
else -> null else -> null
} }
} }
private fun inferCallReturnClass(ref: CallRef): ObjClass? {
return when (val target = ref.target) {
is LocalSlotRef -> nameObjClass[target.name] ?: resolveTypeNameClass(target.name)
is LocalVarRef -> nameObjClass[target.name] ?: resolveTypeNameClass(target.name)
is ConstRef -> target.constValue as? ObjClass
else -> null
}
}
private fun inferMethodCallReturnClass(name: String): ObjClass? = when (name) {
"map",
"mapNotNull",
"filter",
"filterNotNull",
"drop",
"take",
"flatMap",
"flatten",
"sorted",
"sortedBy",
"sortedWith",
"reversed",
"toList",
"shuffle",
"shuffled" -> ObjList.type
"dropLast" -> ObjFlow.type
"takeLast" -> ObjRingBuffer.type
"iterator" -> ObjIterator
"count" -> ObjInt.type
"toSet" -> ObjSet.type
"toMap" -> ObjMap.type
"joinToString" -> ObjString.type
"now",
"truncateToSecond",
"truncateToMinute",
"truncateToMillisecond" -> ObjInstant.type
"toDateTime",
"toTimeZone",
"toUTC",
"parseRFC3339",
"addYears",
"addMonths",
"addDays",
"addHours",
"addMinutes",
"addSeconds" -> ObjDateTime.type
"toInstant" -> ObjInstant.type
"toRFC3339",
"toSortableString",
"toJsonString",
"decodeUtf8",
"toDump",
"toString" -> ObjString.type
"startsWith",
"matches" -> ObjBool.type
"toInt",
"toEpochSeconds" -> ObjInt.type
"toMutable" -> ObjMutableBuffer.type
"seq" -> ObjFlow.type
"encode" -> ObjBitBuffer.type
"assertThrows" -> ObjException.Root
else -> null
}
private fun inferFieldReturnClass(targetClass: ObjClass?, name: String): ObjClass? {
if (targetClass == null) return null
if (targetClass == ObjDynamic.type) return ObjDynamic.type
if (targetClass == ObjInstant.type && (name == "distantFuture" || name == "distantPast")) {
return ObjInstant.type
}
if (targetClass == ObjString.type && name == "re") {
return ObjRegex.type
}
if (targetClass == ObjInt.type || targetClass == ObjReal.type) {
return when (name) {
"day",
"days",
"hour",
"hours",
"minute",
"minutes",
"second",
"seconds",
"millisecond",
"milliseconds",
"microsecond",
"microseconds" -> ObjDuration.type
else -> null
}
}
if (targetClass == ObjDuration.type) {
return when (name) {
"days",
"hours",
"minutes",
"seconds",
"milliseconds",
"microseconds" -> ObjReal.type
else -> null
}
}
if (targetClass == ObjInstant.type) {
return when (name) {
"epochSeconds",
"epochWholeSeconds" -> ObjInt.type
"truncateToSecond",
"truncateToMinute",
"truncateToMillisecond" -> ObjInstant.type
else -> null
}
}
if (targetClass == ObjDateTime.type) {
return when (name) {
"year",
"month",
"day",
"hour",
"minute",
"second",
"dayOfWeek",
"nanosecond" -> ObjInt.type
"timeZone" -> ObjString.type
else -> null
}
}
if (targetClass == ObjException.Root || targetClass.allParentsSet.contains(ObjException.Root)) {
return when (name) {
"message" -> ObjString.type
"stackTrace" -> ObjList.type
else -> null
}
}
if (targetClass == ObjRegex.type && name == "pattern") {
return ObjString.type
}
return null
}
private fun queueExtensionCallableNames(receiverClass: ObjClass, memberName: String) { private fun queueExtensionCallableNames(receiverClass: ObjClass, memberName: String) {
for (cls in receiverClass.mro) { for (cls in receiverClass.mro) {
val name = extensionCallableName(cls.className, memberName) val name = extensionCallableName(cls.className, memberName)

View File

@ -1321,7 +1321,8 @@ class CmdCallSlot(
frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) } frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
} else { } else {
// Pooling for Statement-based callables (lambdas) can still alter closure semantics; keep safe path for now. // Pooling for Statement-based callables (lambdas) can still alter closure semantics; keep safe path for now.
callee.callOn(frame.ensureScope().createChildScope(frame.ensureScope().pos, args = args)) val scope = frame.ensureScope()
callee.callOn(scope.createChildScope(scope.pos, args = args))
} }
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame() frame.syncScopeToFrame()
@ -1360,6 +1361,14 @@ class CmdListLiteral(
} }
} }
private fun decodeMemberId(id: Int): Pair<Int, Boolean> {
return if (id <= -2) {
Pair(-id - 2, true)
} else {
Pair(id, false)
}
}
class CmdGetMemberSlot( class CmdGetMemberSlot(
internal val recvSlot: Int, internal val recvSlot: Int,
internal val fieldId: Int, internal val fieldId: Int,
@ -1369,22 +1378,42 @@ class CmdGetMemberSlot(
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val receiver = frame.slotToObj(recvSlot) val receiver = frame.slotToObj(recvSlot)
val inst = receiver as? ObjInstance val inst = receiver as? ObjInstance
val fieldRec = if (fieldId >= 0) { val cls = receiver as? ObjClass
inst?.fieldRecordForId(fieldId) ?: receiver.objClass.fieldRecordForId(fieldId) val (fieldIdResolved, fieldOnObjClass) = decodeMemberId(fieldId)
val (methodIdResolved, methodOnObjClass) = decodeMemberId(methodId)
val fieldRec = if (fieldIdResolved >= 0) {
when {
inst != null -> inst.fieldRecordForId(fieldIdResolved) ?: inst.objClass.fieldRecordForId(fieldIdResolved)
cls != null && fieldOnObjClass -> cls.objClass.fieldRecordForId(fieldIdResolved)
cls != null -> cls.fieldRecordForId(fieldIdResolved)
else -> receiver.objClass.fieldRecordForId(fieldIdResolved)
}
} else null } else null
val rec = fieldRec ?: run { val rec = fieldRec ?: run {
if (methodId >= 0) { if (methodIdResolved >= 0) {
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId) when {
inst != null -> inst.methodRecordForId(methodIdResolved) ?: inst.objClass.methodRecordForId(methodIdResolved)
cls != null && methodOnObjClass -> cls.objClass.methodRecordForId(methodIdResolved)
cls != null -> cls.methodRecordForId(methodIdResolved)
else -> receiver.objClass.methodRecordForId(methodIdResolved)
}
} else null } else null
} ?: frame.ensureScope().raiseSymbolNotFound("member") } ?: frame.ensureScope().raiseSymbolNotFound("member")
val name = rec.memberName ?: "<member>" val name = rec.memberName ?: "<member>"
suspend fun autoCallIfMethod(resolved: ObjRecord, recv: Obj): Obj {
return if (resolved.type == ObjRecord.Type.Fun && !resolved.isAbstract) {
resolved.value.invoke(frame.ensureScope(), resolved.receiver ?: recv, Arguments.EMPTY, resolved.declaringClass)
} else {
resolved.value
}
}
if (receiver is ObjQualifiedView) { if (receiver is ObjQualifiedView) {
val resolved = receiver.readField(frame.ensureScope(), name) val resolved = receiver.readField(frame.ensureScope(), name)
frame.storeObjResult(dst, resolved.value) frame.storeObjResult(dst, autoCallIfMethod(resolved, receiver))
return return
} }
val resolved = receiver.resolveRecord(frame.ensureScope(), rec, name, rec.declaringClass) val resolved = receiver.resolveRecord(frame.ensureScope(), rec, name, rec.declaringClass)
frame.storeObjResult(dst, resolved.value) frame.storeObjResult(dst, autoCallIfMethod(resolved, receiver))
return return
} }
} }
@ -1398,12 +1427,25 @@ class CmdSetMemberSlot(
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val receiver = frame.slotToObj(recvSlot) val receiver = frame.slotToObj(recvSlot)
val inst = receiver as? ObjInstance val inst = receiver as? ObjInstance
val fieldRec = if (fieldId >= 0) { val cls = receiver as? ObjClass
inst?.fieldRecordForId(fieldId) ?: receiver.objClass.fieldRecordForId(fieldId) val (fieldIdResolved, fieldOnObjClass) = decodeMemberId(fieldId)
val (methodIdResolved, methodOnObjClass) = decodeMemberId(methodId)
val fieldRec = if (fieldIdResolved >= 0) {
when {
inst != null -> inst.fieldRecordForId(fieldIdResolved) ?: inst.objClass.fieldRecordForId(fieldIdResolved)
cls != null && fieldOnObjClass -> cls.objClass.fieldRecordForId(fieldIdResolved)
cls != null -> cls.fieldRecordForId(fieldIdResolved)
else -> receiver.objClass.fieldRecordForId(fieldIdResolved)
}
} else null } else null
val rec = fieldRec ?: run { val rec = fieldRec ?: run {
if (methodId >= 0) { if (methodIdResolved >= 0) {
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId) when {
inst != null -> inst.methodRecordForId(methodIdResolved) ?: inst.objClass.methodRecordForId(methodIdResolved)
cls != null && methodOnObjClass -> cls.objClass.methodRecordForId(methodIdResolved)
cls != null -> cls.methodRecordForId(methodIdResolved)
else -> receiver.objClass.methodRecordForId(methodIdResolved)
}
} else null } else null
} ?: frame.ensureScope().raiseSymbolNotFound("member") } ?: frame.ensureScope().raiseSymbolNotFound("member")
val name = rec.memberName ?: "<member>" val name = rec.memberName ?: "<member>"
@ -1429,8 +1471,14 @@ class CmdCallMemberSlot(
} }
val receiver = frame.slotToObj(recvSlot) val receiver = frame.slotToObj(recvSlot)
val inst = receiver as? ObjInstance val inst = receiver as? ObjInstance
val rec = inst?.methodRecordForId(methodId) val cls = receiver as? ObjClass
?: receiver.objClass.methodRecordForId(methodId) val (methodIdResolved, methodOnObjClass) = decodeMemberId(methodId)
val rec = inst?.methodRecordForId(methodIdResolved)
?: when {
cls != null && methodOnObjClass -> cls.objClass.methodRecordForId(methodIdResolved)
cls != null -> cls.methodRecordForId(methodIdResolved)
else -> receiver.objClass.methodRecordForId(methodIdResolved)
}
?: frame.ensureScope().raiseError("member id $methodId not found on ${receiver.objClass.className}") ?: frame.ensureScope().raiseError("member id $methodId not found on ${receiver.objClass.className}")
val callArgs = frame.buildArguments(argBase, argCount) val callArgs = frame.buildArguments(argBase, argCount)
val name = rec.memberName ?: "<member>" val name = rec.memberName ?: "<member>"
@ -1605,15 +1653,51 @@ class CmdFrame(
} }
private fun resolveModuleScope(scope: Scope): Scope { private fun resolveModuleScope(scope: Scope): Scope {
var current: Scope? = scope val moduleSlotName = fn.scopeSlotNames.indices
var last: Scope = scope .firstOrNull { fn.scopeSlotIsModule.getOrNull(it) == true }
while (current != null) { ?.let { fn.scopeSlotNames[it] }
if (moduleSlotName != null) {
findScopeWithSlot(scope, moduleSlotName)?.let { return it }
}
findModuleScope(scope)?.let { return it }
return scope
}
private fun findScopeWithSlot(scope: Scope, slotName: String): Scope? {
val visited = HashSet<Scope>(16)
val queue = ArrayDeque<Scope>()
queue.add(scope)
while (queue.isNotEmpty()) {
val current = queue.removeFirst()
if (!visited.add(current)) continue
if (current.getSlotIndexOf(slotName) != null) return current
current.parent?.let { queue.add(it) }
if (current is ClosureScope) {
queue.add(current.closureScope)
} else if (current is ApplyScope) {
queue.add(current.applied)
}
}
return null
}
private fun findModuleScope(scope: Scope): Scope? {
val visited = HashSet<Scope>(16)
val queue = ArrayDeque<Scope>()
queue.add(scope)
while (queue.isNotEmpty()) {
val current = queue.removeFirst()
if (!visited.add(current)) continue
if (current is ModuleScope) return current if (current is ModuleScope) return current
if (current.parent is ModuleScope) return current if (current.parent is ModuleScope) return current
last = current current.parent?.let { queue.add(it) }
current = current.parent if (current is ClosureScope) {
queue.add(current.closureScope)
} else if (current is ApplyScope) {
queue.add(current.applied)
} }
return last }
return null
} }
fun ensureScope(): Scope { fun ensureScope(): Scope {
@ -1737,7 +1821,7 @@ class CmdFrame(
scopeDepth -= 1 scopeDepth -= 1
} }
fun getObj(slot: Int): Obj { suspend fun getObj(slot: Int): Obj {
return if (slot < fn.scopeSlotCount) { return if (slot < fn.scopeSlotCount) {
getScopeSlotValue(slot) getScopeSlotValue(slot)
} else { } else {
@ -1755,7 +1839,7 @@ class CmdFrame(
} }
} }
fun getInt(slot: Int): Long { suspend fun getInt(slot: Int): Long {
return if (slot < fn.scopeSlotCount) { return if (slot < fn.scopeSlotCount) {
getScopeSlotValue(slot).toLong() getScopeSlotValue(slot).toLong()
} else { } else {
@ -1786,7 +1870,7 @@ class CmdFrame(
frame.setInt(local, value) frame.setInt(local, value)
} }
fun getReal(slot: Int): Double { suspend fun getReal(slot: Int): Double {
return if (slot < fn.scopeSlotCount) { return if (slot < fn.scopeSlotCount) {
getScopeSlotValue(slot).toDouble() getScopeSlotValue(slot).toDouble()
} else { } else {
@ -1811,7 +1895,7 @@ class CmdFrame(
} }
} }
fun getBool(slot: Int): Boolean { suspend fun getBool(slot: Int): Boolean {
return if (slot < fn.scopeSlotCount) { return if (slot < fn.scopeSlotCount) {
getScopeSlotValue(slot).toBool() getScopeSlotValue(slot).toBool()
} else { } else {
@ -1850,7 +1934,7 @@ class CmdFrame(
addrScopeSlots[addrSlot] = scopeSlot addrScopeSlots[addrSlot] = scopeSlot
} }
fun getAddrObj(addrSlot: Int): Obj { suspend fun getAddrObj(addrSlot: Int): Obj {
return getScopeSlotValueAtAddr(addrSlot) return getScopeSlotValueAtAddr(addrSlot)
} }
@ -1858,7 +1942,7 @@ class CmdFrame(
setScopeSlotValueAtAddr(addrSlot, value) setScopeSlotValueAtAddr(addrSlot, value)
} }
fun getAddrInt(addrSlot: Int): Long { suspend fun getAddrInt(addrSlot: Int): Long {
return getScopeSlotValueAtAddr(addrSlot).toLong() return getScopeSlotValueAtAddr(addrSlot).toLong()
} }
@ -1866,7 +1950,7 @@ class CmdFrame(
setScopeSlotValueAtAddr(addrSlot, ObjInt.of(value)) setScopeSlotValueAtAddr(addrSlot, ObjInt.of(value))
} }
fun getAddrReal(addrSlot: Int): Double { suspend fun getAddrReal(addrSlot: Int): Double {
return getScopeSlotValueAtAddr(addrSlot).toDouble() return getScopeSlotValueAtAddr(addrSlot).toDouble()
} }
@ -1874,7 +1958,7 @@ class CmdFrame(
setScopeSlotValueAtAddr(addrSlot, ObjReal.of(value)) setScopeSlotValueAtAddr(addrSlot, ObjReal.of(value))
} }
fun getAddrBool(addrSlot: Int): Boolean { suspend fun getAddrBool(addrSlot: Int): Boolean {
return getScopeSlotValueAtAddr(addrSlot).toBool() return getScopeSlotValueAtAddr(addrSlot).toBool()
} }
@ -1882,11 +1966,18 @@ class CmdFrame(
setScopeSlotValueAtAddr(addrSlot, if (value) ObjTrue else ObjFalse) setScopeSlotValueAtAddr(addrSlot, if (value) ObjTrue else ObjFalse)
} }
fun slotToObj(slot: Int): Obj { suspend fun slotToObj(slot: Int): Obj {
if (slot < fn.scopeSlotCount) { if (slot < fn.scopeSlotCount) {
return getScopeSlotValue(slot) return getScopeSlotValue(slot)
} }
val local = slot - fn.scopeSlotCount val local = slot - fn.scopeSlotCount
val localName = fn.localSlotNames.getOrNull(local)
if (localName != null) {
val rec = scope.getLocalRecordDirect(localName) ?: scope.localBindings[localName]
if (rec != null && (rec.type == ObjRecord.Type.Delegated || rec.type == ObjRecord.Type.Property || rec.value is ObjProperty)) {
return scope.resolve(rec, localName)
}
}
return when (frame.getSlotTypeCode(local)) { return when (frame.getSlotTypeCode(local)) {
SlotType.INT.code -> ObjInt.of(frame.getInt(local)) SlotType.INT.code -> ObjInt.of(frame.getInt(local))
SlotType.REAL.code -> ObjReal.of(frame.getReal(local)) SlotType.REAL.code -> ObjReal.of(frame.getReal(local))
@ -2067,14 +2158,20 @@ class CmdFrame(
} }
} }
private fun getScopeSlotValue(slot: Int): Obj { private suspend fun getScopeSlotValue(slot: Int): Obj {
val target = scopeTarget(slot) val target = scopeTarget(slot)
val index = ensureScopeSlot(target, slot) val index = ensureScopeSlot(target, slot)
val record = target.getSlotRecord(index) val record = target.getSlotRecord(index)
val direct = record.value val direct = record.value
if (direct is FrameSlotRef) return direct.read() if (direct is FrameSlotRef) return direct.read()
if (direct !== ObjUnset) return direct val name = fn.scopeSlotNames[slot]
val name = fn.scopeSlotNames[slot] ?: return record.value if (direct !== ObjUnset) {
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
return target.resolve(record, name)
}
return direct
}
if (name == null) return record.value
val resolved = target.get(name) ?: return record.value val resolved = target.get(name) ?: return record.value
if (resolved.value !== ObjUnset) { if (resolved.value !== ObjUnset) {
target.updateSlotFor(name, resolved) target.updateSlotFor(name, resolved)
@ -2082,15 +2179,21 @@ class CmdFrame(
return resolved.value return resolved.value
} }
private fun getScopeSlotValueAtAddr(addrSlot: Int): Obj { private suspend fun getScopeSlotValueAtAddr(addrSlot: Int): Obj {
val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved") val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved")
val index = addrIndices[addrSlot] val index = addrIndices[addrSlot]
val record = target.getSlotRecord(index) val record = target.getSlotRecord(index)
val direct = record.value val direct = record.value
if (direct is FrameSlotRef) return direct.read() if (direct is FrameSlotRef) return direct.read()
if (direct !== ObjUnset) return direct
val slotId = addrScopeSlots[addrSlot] val slotId = addrScopeSlots[addrSlot]
val name = fn.scopeSlotNames[slotId] ?: return record.value val name = fn.scopeSlotNames.getOrNull(slotId)
if (direct !== ObjUnset) {
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
return target.resolve(record, name)
}
return direct
}
if (name == null) return record.value
val resolved = target.get(name) ?: return record.value val resolved = target.get(name) ?: return record.value
if (resolved.value !== ObjUnset) { if (resolved.value !== ObjUnset) {
target.updateSlotFor(name, resolved) target.updateSlotFor(name, resolved)

View File

@ -65,7 +65,8 @@ open class Obj {
fun isInstanceOf(someClass: Obj) = someClass === objClass || fun isInstanceOf(someClass: Obj) = someClass === objClass ||
objClass.allParentsSet.contains(someClass) || objClass.allParentsSet.contains(someClass) ||
someClass == rootObjectType || someClass == rootObjectType ||
(someClass is ObjClass && objClass.allImplementingNames.contains(someClass.className)) (someClass is ObjClass && (objClass.allImplementingNames.contains(someClass.className) ||
objClass.className == someClass.className))
fun isInstanceOf(className: String) = fun isInstanceOf(className: String) =
objClass.mro.any { it.className == className } || objClass.mro.any { it.className == className } ||
@ -141,6 +142,15 @@ open class Obj {
} }
} }
} }
scope.findExtension(objClass, name)?.let { ext ->
if (ext.type == ObjRecord.Type.Property) {
if (args.isEmpty()) {
return (ext.value as ObjProperty).callGetter(scope, this, ext.declaringClass)
}
} else if (ext.type != ObjRecord.Type.Delegated) {
return ext.value.invoke(scope, this, args, ext.declaringClass)
}
}
return onNotFoundResult?.invoke() return onNotFoundResult?.invoke()
?: scope.raiseError( ?: scope.raiseError(
@ -472,6 +482,14 @@ open class Obj {
} }
} }
} }
scope.findExtension(objClass, name)?.let { ext ->
return if (ext.type == ObjRecord.Type.Property) {
val prop = ext.value as ObjProperty
ObjRecord(prop.callGetter(scope, this, ext.declaringClass), isMutable = false)
} else {
ext.copy(value = ext.value.invoke(scope, this, Arguments.EMPTY, ext.declaringClass))
}
}
scope.raiseError( scope.raiseError(
"no such field: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}" "no such field: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}"
@ -486,6 +504,7 @@ open class Obj {
if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") { if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") {
val wrapper = object : Statement() { val wrapper = object : Statement() {
override val pos: Pos = Pos.builtIn override val pos: Pos = Pos.builtIn
override suspend fun execute(s: Scope): Obj { override suspend fun execute(s: Scope): Obj {
val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj
val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray() val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray()
@ -587,16 +606,19 @@ open class Obj {
scope.raiseNotImplemented() scope.raiseNotImplemented()
} }
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj = suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj {
if (PerfFlags.SCOPE_POOL) val usePool = PerfFlags.SCOPE_POOL && this !is Statement
return if (usePool) {
scope.withChildFrame(args, newThisObj = thisObj) { child -> scope.withChildFrame(args, newThisObj = thisObj) { child ->
if (declaringClass != null) child.currentClassCtx = declaringClass if (declaringClass != null) child.currentClassCtx = declaringClass
callOn(child) callOn(child)
} }
else } else {
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also { callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
if (declaringClass != null) it.currentClassCtx = declaringClass if (declaringClass != null) it.currentClassCtx = declaringClass
}) })
}
}
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj = suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
callOn( callOn(

View File

@ -92,17 +92,24 @@ open class ObjException(
companion object { companion object {
private var stackTraceCaptureDepth = 0
suspend fun captureStackTrace(scope: Scope): ObjList { suspend fun captureStackTrace(scope: Scope): ObjList {
val result = ObjList() val result = ObjList()
val maybeCls = scope.get("StackTraceEntry")?.value as? ObjClass val nestedCapture = stackTraceCaptureDepth > 0
stackTraceCaptureDepth += 1
val maybeCls = if (nestedCapture) null else scope.get("StackTraceEntry")?.value as? ObjClass
var s: Scope? = scope var s: Scope? = scope
var lastPos: Pos? = null var lastPos: Pos? = null
try {
while (s != null) { while (s != null) {
val pos = s.pos val pos = s.pos
if (pos != lastPos && !pos.currentLine.isEmpty()) { if (pos != lastPos && !pos.currentLine.isEmpty()) {
if( (lastPos == null || (lastPos.source != pos.source || lastPos.line != pos.line)) ) { if (lastPos == null || (lastPos.source != pos.source || lastPos.line != pos.line)) {
val fallback =
ObjString("#${pos.source.objSourceName}:${pos.line+1}:${pos.column+1}: ${pos.currentLine}")
if (maybeCls != null) { if (maybeCls != null) {
try {
result.list += maybeCls.callWithArgs( result.list += maybeCls.callWithArgs(
scope, scope,
pos.source.objSourceName, pos.source.objSourceName,
@ -110,9 +117,13 @@ open class ObjException(
ObjInt(pos.column.toLong()), ObjInt(pos.column.toLong()),
ObjString(pos.currentLine) ObjString(pos.currentLine)
) )
} catch (e: Throwable) {
// Fallback textual entry if StackTraceEntry fails to instantiate
result.list += fallback
}
} else { } else {
// Fallback textual entry if StackTraceEntry class is not available in this scope // Fallback textual entry if StackTraceEntry class is not available in this scope
result.list += ObjString("#${pos.source.objSourceName}:${pos.line+1}:${pos.column+1}: ${pos.currentLine}") result.list += fallback
} }
lastPos = pos lastPos = pos
} }
@ -120,6 +131,9 @@ open class ObjException(
s = s.parent s = s.parent
} }
return result return result
} finally {
stackTraceCaptureDepth -= 1
}
} }
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) { class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {

View File

@ -174,6 +174,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") { if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") {
val wrapper = object : Statement() { val wrapper = object : Statement() {
override val pos: Pos = Pos.builtIn override val pos: Pos = Pos.builtIn
override suspend fun execute(s: Scope): Obj { override suspend fun execute(s: Scope): Obj {
val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj
val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray() val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray()
@ -364,6 +365,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// Fast path for public members when outside any class context // Fast path for public members when outside any class context
if (caller == null) { if (caller == null) {
objClass.members[name]?.let { rec ->
if (rec.visibility == Visibility.Public && !rec.isAbstract) {
val decl = rec.declaringClass
if (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(instanceScope, this, args, decl)
}
}
}
objClass.publicMemberResolution[name]?.let { key -> objClass.publicMemberResolution[name]?.let { key ->
methodRecordForKey(key)?.let { rec -> methodRecordForKey(key)?.let { rec ->
if (rec.visibility == Visibility.Public && !rec.isAbstract) { if (rec.visibility == Visibility.Public && !rec.isAbstract) {

View File

@ -286,5 +286,3 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
} }
} }

View File

@ -445,40 +445,66 @@ class CastRef(
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)
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${t} is not the class instance")
// unwrap qualified views // unwrap qualified views
val v = when (v0) { val v = when (v0) {
is ObjQualifiedView -> v0.instance is ObjQualifiedView -> v0.instance
else -> v0 else -> v0
} }
return if (v.isInstanceOf(target)) { return when (t) {
is ObjClass -> {
if (v.isInstanceOf(t)) {
// For instances, return a qualified view to enforce ancestor-start dispatch // For instances, return a qualified view to enforce ancestor-start dispatch
if (v is ObjInstance) ObjQualifiedView(v, target).asReadonly else v.asReadonly if (v is ObjInstance) ObjQualifiedView(v, t).asReadonly else v.asReadonly
} else { } else {
if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError( if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError(
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${target.className}" "Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${t.className}"
) )
} }
} }
is ObjTypeExpr -> {
if (matchesTypeDecl(scope, v, t.typeDecl)) {
v.asReadonly
} else {
if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError(
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${t.typeDecl}"
)
}
}
else -> scope.raiseClassCastError("${t} is not the class instance")
}
}
override suspend fun evalValue(scope: Scope): Obj { override suspend fun evalValue(scope: Scope): Obj {
val v0 = valueRef.evalValue(scope) val v0 = valueRef.evalValue(scope)
val t = typeRef.evalValue(scope) val t = typeRef.evalValue(scope)
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${t} is not the class instance")
// unwrap qualified views // unwrap qualified views
val v = when (v0) { val v = when (v0) {
is ObjQualifiedView -> v0.instance is ObjQualifiedView -> v0.instance
else -> v0 else -> v0
} }
return if (v.isInstanceOf(target)) { return when (t) {
is ObjClass -> {
if (v.isInstanceOf(t)) {
// For instances, return a qualified view to enforce ancestor-start dispatch // For instances, return a qualified view to enforce ancestor-start dispatch
if (v is ObjInstance) ObjQualifiedView(v, target) else v if (v is ObjInstance) ObjQualifiedView(v, t) else v
} else { } else {
if (isNullable) ObjNull else scope.raiseClassCastError( if (isNullable) ObjNull else scope.raiseClassCastError(
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${target.className}" "Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${t.className}"
) )
} }
} }
is ObjTypeExpr -> {
if (matchesTypeDecl(scope, v, t.typeDecl)) {
v
} else {
if (isNullable) ObjNull else scope.raiseClassCastError(
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${t.typeDecl}"
)
}
}
else -> scope.raiseClassCastError("${t} is not the class instance")
}
}
} }
/** Type expression reference used for `is` checks (including unions/intersections). */ /** Type expression reference used for `is` checks (including unions/intersections). */
@ -1175,6 +1201,10 @@ class FieldRef(
if (rec.receiver != null && rec.declaringClass != null) { if (rec.receiver != null && rec.declaringClass != null) {
return rec.receiver!!.resolveRecord(scope, rec, name, rec.declaringClass).value return rec.receiver!!.resolveRecord(scope, rec, name, rec.declaringClass).value
} }
if (rec.type == ObjRecord.Type.Fun && !rec.isAbstract) {
val receiver = rec.receiver ?: base
return rec.value.invoke(scope, receiver, Arguments.EMPTY, rec.declaringClass)
}
return rec.value return rec.value
} }
@ -2300,6 +2330,11 @@ class LocalSlotRef(
scope.raiseError("slot index out of range for $name") scope.raiseError("slot index out of range for $name")
} }
val rec = owner.getSlotRecord(slotIndex) val rec = owner.getSlotRecord(slotIndex)
val direct = owner.getLocalRecordDirect(name)
if (direct != null && direct !== rec) {
owner.updateSlotFor(name, direct)
return direct
}
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access")) scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
} }
@ -2324,6 +2359,11 @@ class LocalSlotRef(
scope.raiseError("slot index out of range for $name") scope.raiseError("slot index out of range for $name")
} }
val rec = owner.getSlotRecord(slotIndex) val rec = owner.getSlotRecord(slotIndex)
val direct = owner.getLocalRecordDirect(name)
if (direct != null && direct !== rec) {
owner.updateSlotFor(name, direct)
return scope.resolve(direct, name)
}
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access")) scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
} }

View File

@ -68,11 +68,17 @@ abstract class ImportProvider(
suspend fun newStdScope(pos: Pos = Pos.builtIn): Scope = suspend fun newStdScope(pos: Pos = Pos.builtIn): Scope =
cachedStdScope.get { cachedStdScope.get {
newModuleAt(pos).also { val module = newModuleAt(pos)
it.eval("import lyng.stdlib\n") val stdlib = prepareImport(pos, "lyng.stdlib", null)
val plan = LinkedHashMap<String, Int>()
for ((name, record) in stdlib.objects) {
if (!record.visibility.isPublic) continue
plan[name] = plan.size
} }
if (plan.isNotEmpty()) module.applySlotPlan(plan)
stdlib.importInto(module, null)
module
}.createChildScope() }.createChildScope()
} }