Step 1: module import slots and extension lookup
This commit is contained in:
parent
a557d0cc59
commit
7b70a37e90
@ -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.
|
||||
Mark items as you implement them. Priorities are ordered by expected simplicity.
|
||||
Goal: migrate :lynglib compiler/runtime so values live in frame slots and bytecode is the default execution path.
|
||||
|
||||
## Priority 1: Quick wins (local changes)
|
||||
- [ ] Implement bytecode emission for `DelegatedVarDeclStatement`.
|
||||
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt:3284`, `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1667`
|
||||
- [ ] Implement bytecode emission for `DestructuringVarDeclStatement`.
|
||||
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt:3285`, `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1667`
|
||||
- [ ] Ensure `ExtensionPropertyDeclStatement` is handled in both value and no-value bytecode paths.
|
||||
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt:3332`
|
||||
## Step 1: Imports as module slots (done)
|
||||
- [x] Seed module slot plans from import bindings (lazy, unused imports do not allocate).
|
||||
- [x] Avoid mutating scopes during compile-time imports; bind slots at runtime instead.
|
||||
- [x] Make runtime member access honor extensions (methods + properties).
|
||||
- [x] Ensure class members (ObjClass instances) resolve by slot id in bytecode runtime.
|
||||
- [x] Expose `Iterator` in root scope so stdlib externs bind at runtime.
|
||||
|
||||
## Priority 2: Conservative wrapper guards to relax
|
||||
- [ ] Allow wrapping `BreakStatement` / `ContinueStatement` / `ReturnStatement` where bytecode already supports them.
|
||||
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1608`
|
||||
- [ ] Revisit `containsLoopControl` as a hard blocker for wrapping (once label handling is verified).
|
||||
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1542`
|
||||
## Step 2: Class-scope member refs + qualified-this refs (pending)
|
||||
- [ ] Bytecode-compile `ClassScopeMemberRef` (currently forced to AST in `Compiler.containsUnsupportedRef`).
|
||||
- [ ] Bytecode-compile `QualifiedThisFieldSlotRef` / `QualifiedThisMethodSlotCallRef`.
|
||||
- [ ] Ensure slot resolution uses class member ids, not scope lookup; no fallback opcodes.
|
||||
- [ ] Add coverage for class static access + qualified-this access to keep JVM tests green.
|
||||
|
||||
## Priority 3: Medium complexity statements
|
||||
- [ ] Implement bytecode support for `TryStatement`.
|
||||
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1698`, `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt:3290`
|
||||
- [ ] Expand `WhenStatement` condition coverage (remove "unsupported condition" paths).
|
||||
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1699`
|
||||
## Step 3: Expand bytecode coverage for control flow + literals (pending)
|
||||
- [ ] Add bytecode support for `TryStatement` (catch/finally) in `Compiler.containsUnsupportedForBytecode` and `BytecodeCompiler`.
|
||||
- [ ] Support `WhenStatement` conditions beyond the current limited set.
|
||||
- [ ] Add map literal spread support (currently throws in `BytecodeCompiler`).
|
||||
- [ ] Remove remaining `BytecodeCompileException` cases for common member access (missing id paths).
|
||||
|
||||
## Priority 4: Ref-level blockers
|
||||
- [ ] Support dynamic member access in bytecode (`FieldRef` / `MethodCallRef` on `ObjDynamic`).
|
||||
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1735`
|
||||
- [ ] Implement bytecode for qualified/captured member refs:
|
||||
- `QualifiedThisMethodSlotCallRef`
|
||||
- `QualifiedThisFieldSlotRef`
|
||||
- `ClassScopeMemberRef`
|
||||
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1759`
|
||||
## Known bytecode gaps (from current guards)
|
||||
- [ ] `TryStatement` is always excluded by `Compiler.containsUnsupportedForBytecode`.
|
||||
- [ ] `ClassScopeMemberRef` and qualified-this refs are excluded by `Compiler.containsUnsupportedRef`.
|
||||
- [ ] `BytecodeCompiler` rejects map literal spreads and some argument expressions.
|
||||
- [ ] Member access still fails when compile-time receiver class cannot be resolved.
|
||||
|
||||
## Priority 5: Wrapping policy improvements
|
||||
- [ ] Allow partial script wrapping (wrap supported statements even when others are unsupported).
|
||||
- References: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:1333`
|
||||
- [ ] 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`
|
||||
## Validation
|
||||
- [ ] `./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.
|
||||
- [ ] Baseline full suite: currently 46 failures on `:lynglib:jvmTest` (run 2026-02-08); keep targeted tests green until the baseline is addressed.
|
||||
|
||||
@ -37,8 +37,14 @@ class BlockStatement(
|
||||
?: applyScope.callScope.resolveCaptureRecord(capture.name)
|
||||
} else {
|
||||
scope.resolveCaptureRecord(capture.name)
|
||||
} ?: (applyScope?.callScope ?: scope)
|
||||
.raiseSymbolNotFound("symbol ${capture.name} not found")
|
||||
}
|
||||
if (rec == null) {
|
||||
if (scope.getSlotIndexOf(capture.name) == null && scope.getLocalRecordDirect(capture.name) == null) {
|
||||
continue
|
||||
}
|
||||
(applyScope?.callScope ?: scope)
|
||||
.raiseSymbolNotFound("symbol ${capture.name} not found")
|
||||
}
|
||||
target.updateSlotFor(capture.name, rec)
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,6 +107,12 @@ class Compiler(
|
||||
if (added && localDeclCountStack.isNotEmpty()) {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -124,6 +130,20 @@ class Compiler(
|
||||
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 val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, 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() {
|
||||
val plan = moduleSlotPlan() ?: return
|
||||
val saved = cc.savePos()
|
||||
@ -448,11 +482,11 @@ class Compiler(
|
||||
val scopeRec = seedScope?.get(name) ?: importManager.rootScope.get(name)
|
||||
val clsFromScope = scopeRec?.value as? ObjClass
|
||||
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 {
|
||||
null
|
||||
}
|
||||
val cls = clsFromScope ?: clsFromImports ?: return null
|
||||
val cls = clsFromScope ?: clsFromImports ?: resolveClassByName(name) ?: return null
|
||||
val fieldIds = cls.instanceFieldIdMap()
|
||||
val methodIds = cls.instanceMethodIdMap(includeAbstract = true)
|
||||
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
|
||||
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(
|
||||
name,
|
||||
moduleLoc.slot,
|
||||
@ -854,6 +894,83 @@ class Compiler(
|
||||
val ids = resolveMemberIds(name, pos, null)
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName())
|
||||
}
|
||||
val implicitTypeFromFunc = implicitReceiverTypeForMember(name)
|
||||
val hasImplicitClassMember = classCtx != null && hasImplicitThisMember(name, classCtx.name)
|
||||
if (implicitTypeFromFunc == null && !hasImplicitClassMember) {
|
||||
val modulePlan = moduleSlotPlan()
|
||||
val moduleEntry = modulePlan?.slots?.get(name)
|
||||
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(
|
||||
moduleEntry.index,
|
||||
slotPlanStack.size - 1,
|
||||
modulePlan.id,
|
||||
moduleEntry.isMutable,
|
||||
moduleEntry.isDelegated
|
||||
)
|
||||
captureLocalRef(name, moduleLoc, pos)?.let { ref ->
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
val ref = LocalSlotRef(
|
||||
name,
|
||||
moduleLoc.slot,
|
||||
moduleLoc.scopeId,
|
||||
moduleLoc.isMutable,
|
||||
moduleLoc.isDelegated,
|
||||
pos,
|
||||
strictSlotRefs
|
||||
)
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
resolveImportBinding(name, pos)?.let { resolved ->
|
||||
val sourceRecord = resolved.record
|
||||
if (modulePlan != null && !modulePlan.slots.containsKey(name)) {
|
||||
val seedSlotIndex = if (resolved.binding.source is ImportBindingSource.Seed) {
|
||||
seedScope?.getSlotIndexOf(name)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (seedSlotIndex != 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(
|
||||
name,
|
||||
slot.slot,
|
||||
slot.scopeId,
|
||||
slot.isMutable,
|
||||
slot.isDelegated,
|
||||
pos,
|
||||
strictSlotRefs
|
||||
)
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
}
|
||||
}
|
||||
if (classCtx != null) {
|
||||
val implicitType = classCtx.name
|
||||
if (hasImplicitThisMember(name, implicitType)) {
|
||||
@ -863,7 +980,7 @@ class Compiler(
|
||||
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, preferredType)
|
||||
}
|
||||
}
|
||||
val implicitType = implicitReceiverTypeForMember(name)
|
||||
val implicitType = implicitTypeFromFunc
|
||||
if (implicitType != null) {
|
||||
resolutionSink?.referenceMember(name, pos, implicitType)
|
||||
val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
|
||||
@ -873,52 +990,6 @@ class Compiler(
|
||||
resolutionSink?.referenceMember(name, pos, classCtx.name)
|
||||
return ClassScopeMemberRef(name, pos, classCtx.name)
|
||||
}
|
||||
val modulePlan = moduleSlotPlan()
|
||||
val moduleEntry = modulePlan?.slots?.get(name)
|
||||
if (moduleEntry != null) {
|
||||
val moduleLoc = SlotLocation(
|
||||
moduleEntry.index,
|
||||
slotPlanStack.size - 1,
|
||||
modulePlan.id,
|
||||
moduleEntry.isMutable,
|
||||
moduleEntry.isDelegated
|
||||
)
|
||||
captureLocalRef(name, moduleLoc, pos)?.let { ref ->
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
val ref = LocalSlotRef(
|
||||
name,
|
||||
moduleLoc.slot,
|
||||
moduleLoc.scopeId,
|
||||
moduleLoc.isMutable,
|
||||
moduleLoc.isDelegated,
|
||||
pos,
|
||||
strictSlotRefs
|
||||
)
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
val rootRecord = importManager.rootScope.objects[name]
|
||||
if (rootRecord != null && rootRecord.visibility.isPublic) {
|
||||
modulePlan?.let { plan ->
|
||||
declareSlotNameIn(plan, name, rootRecord.isMutable, rootRecord.type == ObjRecord.Type.Delegated)
|
||||
}
|
||||
val rootSlot = lookupSlotLocation(name)
|
||||
if (rootSlot != null) {
|
||||
val ref = LocalSlotRef(
|
||||
name,
|
||||
rootSlot.slot,
|
||||
rootSlot.scopeId,
|
||||
rootSlot.isMutable,
|
||||
rootSlot.isDelegated,
|
||||
pos,
|
||||
strictSlotRefs
|
||||
)
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
}
|
||||
val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody }
|
||||
if (classContext && extensionNames.contains(name)) {
|
||||
resolutionSink?.referenceMember(name, pos)
|
||||
@ -960,7 +1031,10 @@ class Compiler(
|
||||
private val seedScope: Scope? = settings.seedScope
|
||||
private var resolutionScriptDepth = 0
|
||||
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>>()
|
||||
|
||||
// --- 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 {
|
||||
if (seedScope != null) return false
|
||||
if (importManager !== Script.defaultImportManager) return false
|
||||
@ -1216,15 +1402,25 @@ class Compiler(
|
||||
val needsSlotPlan = slotPlanStack.isEmpty()
|
||||
if (needsSlotPlan) {
|
||||
slotPlanStack.add(SlotPlan(mutableMapOf(), 0, nextScopeId++))
|
||||
declareSlotNameIn(slotPlanStack.last(), "__PACKAGE__", isMutable = false, isDelegated = false)
|
||||
declareSlotNameIn(slotPlanStack.last(), "$~", isMutable = true, isDelegated = false)
|
||||
seedScope?.let { seedSlotPlanFromScope(it, includeParents = true) }
|
||||
seedSlotPlanFromScope(importManager.rootScope)
|
||||
seedScope?.let { scope ->
|
||||
if (scope !is ModuleScope) {
|
||||
seedSlotPlanFromSeedScope(scope)
|
||||
}
|
||||
}
|
||||
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()) {
|
||||
val stdlib = importManager.prepareImport(start, "lyng.stdlib", null)
|
||||
seedResolutionFromScope(stdlib, start)
|
||||
seedSlotPlanFromScope(stdlib)
|
||||
importedScopes.add(stdlib)
|
||||
seedNameObjClassFromScope(stdlib)
|
||||
importedModules.add(ImportedModule(stdlib, start))
|
||||
}
|
||||
predeclareTopLevelSymbols()
|
||||
}
|
||||
@ -1293,16 +1489,8 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
val module = importManager.prepareImport(pos, name, null)
|
||||
importedScopes.add(module)
|
||||
importedModules.add(ImportedModule(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
|
||||
}
|
||||
}
|
||||
@ -1336,7 +1524,8 @@ class Compiler(
|
||||
statements.isNotEmpty() &&
|
||||
codeContexts.lastOrNull() is CodeContext.Module &&
|
||||
resolutionScriptDepth == 1 &&
|
||||
statements.none { containsUnsupportedForBytecode(it) }
|
||||
statements.none { containsUnsupportedForBytecode(it) } &&
|
||||
statements.none { containsDelegatedRefs(it) }
|
||||
val finalStatements = if (wrapScriptBytecode) {
|
||||
val unwrapped = statements.map { unwrapBytecodeDeep(it) }
|
||||
val block = InlineBlockStatement(unwrapped, start)
|
||||
@ -1354,7 +1543,8 @@ class Compiler(
|
||||
} else {
|
||||
statements
|
||||
}
|
||||
Script(start, finalStatements, modulePlan)
|
||||
val moduleRefs = importedModules.map { ImportBindingSource.Module(it.scope.packageName, it.pos) }
|
||||
Script(start, finalStatements, modulePlan, importBindings.toMap(), moduleRefs)
|
||||
}.also {
|
||||
// Best-effort script end notification (use current position)
|
||||
miniSink?.onScriptEnd(
|
||||
@ -1415,8 +1605,23 @@ class Compiler(
|
||||
val candidates = mutableListOf(typeName)
|
||||
cls?.mro?.forEach { candidates.add(it.className) }
|
||||
for (baseName in candidates) {
|
||||
val wrapperName = extensionCallableName(baseName, memberName)
|
||||
if (seedScope?.get(wrapperName) != null || importManager.rootScope.get(wrapperName) != null) {
|
||||
val wrapperNames = listOf(
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1578,8 +1783,8 @@ class Compiler(
|
||||
}
|
||||
addScope(seedScope)
|
||||
addScope(importManager.rootScope)
|
||||
for (scope in importedScopes) {
|
||||
addScope(scope)
|
||||
for (module in importedModules) {
|
||||
addScope(module.scope)
|
||||
}
|
||||
for (name in compileClassInfos.keys) {
|
||||
val cls = resolveClassByName(name) ?: continue
|
||||
@ -1730,8 +1935,11 @@ class Compiler(
|
||||
containsUnsupportedRef(ref.condition) || containsUnsupportedRef(ref.ifTrue) || containsUnsupportedRef(ref.ifFalse)
|
||||
is ElvisRef -> containsUnsupportedRef(ref.left) || containsUnsupportedRef(ref.right)
|
||||
is FieldRef -> {
|
||||
val receiverClass = resolveReceiverClassForMember(ref.target)
|
||||
val receiverClass = resolveReceiverClassForMember(ref.target) ?: 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)
|
||||
}
|
||||
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 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 -> {
|
||||
val receiverClass = resolveReceiverClassForMember(ref.receiver)
|
||||
if (ref.name == "delay") return true
|
||||
val receiverClass = resolveReceiverClassForMember(ref.receiver) ?: 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) }
|
||||
}
|
||||
is ImplicitThisMethodCallRef -> true
|
||||
is QualifiedThisMethodSlotCallRef -> true
|
||||
is QualifiedThisFieldSlotRef -> true
|
||||
is ClassScopeMemberRef -> true
|
||||
@ -1766,7 +1986,7 @@ class Compiler(
|
||||
is ExpressionStatement -> containsDelegatedRefs(target.ref)
|
||||
is BlockStatement -> target.statements().any { containsDelegatedRefs(it) }
|
||||
is VarDeclStatement -> target.initializer?.let { containsDelegatedRefs(it) } ?: false
|
||||
is DelegatedVarDeclStatement -> containsDelegatedRefs(target.initializer)
|
||||
is DelegatedVarDeclStatement -> true
|
||||
is DestructuringVarDeclStatement -> containsDelegatedRefs(target.initializer)
|
||||
is IfStatement -> {
|
||||
containsDelegatedRefs(target.condition) ||
|
||||
@ -1812,6 +2032,18 @@ class Compiler(
|
||||
private fun containsDelegatedRefs(ref: ObjRef): Boolean {
|
||||
return when (ref) {
|
||||
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 UnaryOpRef -> containsDelegatedRefs(ref.a)
|
||||
is CastRef -> containsDelegatedRefs(ref.castValueRef()) || containsDelegatedRefs(ref.castTypeRef())
|
||||
@ -1826,7 +2058,14 @@ class Compiler(
|
||||
is ConditionalRef ->
|
||||
containsDelegatedRefs(ref.condition) || containsDelegatedRefs(ref.ifTrue) || containsDelegatedRefs(ref.ifFalse)
|
||||
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 ListLiteralRef -> ref.entries().any {
|
||||
when (it) {
|
||||
@ -1841,7 +2080,14 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
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)
|
||||
else -> false
|
||||
}
|
||||
@ -3939,6 +4185,15 @@ class Compiler(
|
||||
if (targetClass == ObjInstant.type && (name == "distantFuture" || name == "distantPast")) {
|
||||
return ObjInstant.type
|
||||
}
|
||||
if (targetClass == ObjInstant.type && name in listOf(
|
||||
"truncateToMinute",
|
||||
"truncateToSecond",
|
||||
"truncateToMillisecond",
|
||||
"truncateToMicrosecond"
|
||||
)
|
||||
) {
|
||||
return ObjInstant.type
|
||||
}
|
||||
if (targetClass == ObjString.type && name == "re") {
|
||||
return ObjRegex.type
|
||||
}
|
||||
@ -4020,6 +4275,7 @@ class Compiler(
|
||||
if (isAllowedObjectMember(memberName)) return
|
||||
throw ScriptError(pos, "member access requires compile-time receiver type: $memberName")
|
||||
}
|
||||
registerExtensionWrapperBindings(receiverClass, memberName, pos)
|
||||
if (receiverClass == Obj.rootObjectType) {
|
||||
val allowed = isAllowedObjectMember(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 {
|
||||
return when (memberName) {
|
||||
"toString",
|
||||
@ -4489,7 +4768,7 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
val implicitThisTypeName = currentImplicitThisTypeName()
|
||||
return when (left) {
|
||||
val result = when (left) {
|
||||
is ImplicitThisMemberRef ->
|
||||
if (left.methodId == null && left.fieldId != null) {
|
||||
CallRef(left, args, detectedBlockArgument, isOptional)
|
||||
@ -4550,6 +4829,7 @@ class Compiler(
|
||||
}
|
||||
else -> CallRef(left, args, detectedBlockArgument, isOptional)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun inferReceiverTypeFromArgs(args: List<ParsedArgument>): String? {
|
||||
@ -5930,6 +6210,14 @@ class Compiler(
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
// the main statement should create custom ObjClass instance with field
|
||||
// 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
|
||||
val parentClasses = baseSpecs.map { baseSpec ->
|
||||
val rec = scope[baseSpec.name]
|
||||
@ -6566,7 +6854,11 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
val fnStatements = rawFnStatements?.let { stmt ->
|
||||
if (useBytecodeStatements && !containsUnsupportedForBytecode(stmt)) {
|
||||
if (useBytecodeStatements &&
|
||||
parentContext !is CodeContext.ClassBody &&
|
||||
!containsUnsupportedForBytecode(stmt) &&
|
||||
!containsDelegatedRefs(stmt)
|
||||
) {
|
||||
val paramKnownClasses = mutableMapOf<String, ObjClass>()
|
||||
for (param in argsDeclaration.params) {
|
||||
val cls = resolveTypeDeclObjClass(param.type) ?: continue
|
||||
@ -6630,6 +6922,19 @@ class Compiler(
|
||||
val fnCreateStatement = object : Statement() {
|
||||
override val pos: Pos = start
|
||||
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) {
|
||||
val accessType = ObjString("Callable")
|
||||
val initValue = delegateExpression!!.execute(context)
|
||||
@ -6758,7 +7063,9 @@ class Compiler(
|
||||
)
|
||||
execScope.currentClassCtx = cls
|
||||
compiledFnBody.execute(execScope)
|
||||
} ?: compiledFnBody.execute(thisObj.autoInstanceScope(this))
|
||||
} ?: run {
|
||||
compiledFnBody.execute(thisObj.autoInstanceScope(this))
|
||||
}
|
||||
} finally {
|
||||
this.currentClassCtx = savedCtx
|
||||
}
|
||||
@ -7096,8 +7403,8 @@ class Compiler(
|
||||
private fun resolveClassByName(name: String): ObjClass? {
|
||||
val rec = seedScope?.get(name) ?: importManager.rootScope.get(name)
|
||||
(rec?.value as? ObjClass)?.let { return it }
|
||||
for (scope in importedScopes.asReversed()) {
|
||||
val imported = scope.get(name)
|
||||
for (module in importedModules.asReversed()) {
|
||||
val imported = module.scope.get(name)
|
||||
(imported?.value as? ObjClass)?.let { return it }
|
||||
}
|
||||
val info = compileClassInfos[name] ?: return null
|
||||
|
||||
@ -33,6 +33,8 @@ class ModuleScope(
|
||||
|
||||
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]
|
||||
* which checks symbol availability and accessibility prior to execution.
|
||||
@ -92,4 +94,3 @@ class ModuleScope(
|
||||
super.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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?> {
|
||||
if (plan.isEmpty()) return emptyMap()
|
||||
val maxIndex = plan.values.maxOrNull() ?: return emptyMap()
|
||||
|
||||
@ -34,13 +34,50 @@ class Script(
|
||||
private val statements: List<Statement> = emptyList(),
|
||||
private val moduleSlotPlan: Map<String, Int> = emptyMap(),
|
||||
private val importBindings: Map<String, ImportBinding> = emptyMap(),
|
||||
private val importedModules: List<ImportBindingSource.Module> = emptyList(),
|
||||
// private val catchReturn: Boolean = false,
|
||||
) : Statement() {
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
if (moduleSlotPlan.isNotEmpty()) {
|
||||
scope.applySlotPlan(moduleSlotPlan)
|
||||
seedModuleSlots(scope)
|
||||
val isModuleScope = scope is ModuleScope
|
||||
val shouldSeedModule = isModuleScope || scope.thisObj === ObjVoid
|
||||
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
|
||||
for (s in statements) {
|
||||
@ -50,37 +87,24 @@ class Script(
|
||||
}
|
||||
|
||||
private suspend fun seedModuleSlots(scope: Scope) {
|
||||
if (importBindings.isNotEmpty()) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
if (importBindings.isEmpty() && importedModules.isEmpty()) return
|
||||
seedImportBindings(scope)
|
||||
}
|
||||
|
||||
private suspend fun seedImportBindings(scope: Scope) {
|
||||
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) {
|
||||
val record = when (val source = binding.source) {
|
||||
is ImportBindingSource.Module -> {
|
||||
val module = provider.prepareImport(source.pos, source.name, null)
|
||||
importedModules.add(module)
|
||||
module.objects[binding.symbol]?.takeIf { it.visibility.isPublic }
|
||||
?: scope.raiseSymbolNotFound("symbol ${source.name}.${binding.symbol} not found")
|
||||
}
|
||||
@ -99,6 +123,15 @@ class Script(
|
||||
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? {
|
||||
@ -116,6 +149,15 @@ class Script(
|
||||
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
|
||||
|
||||
suspend fun execute() = execute(
|
||||
@ -460,6 +502,7 @@ class Script(
|
||||
// interfaces
|
||||
addConst("Iterable", ObjIterable)
|
||||
addConst("Collection", ObjCollection)
|
||||
addConst("Iterator", ObjIterator)
|
||||
addConst("Array", ObjArray)
|
||||
addConst("RingBuffer", ObjRingBuffer.type)
|
||||
addConst("Class", ObjClassType)
|
||||
|
||||
@ -211,6 +211,12 @@ class BytecodeCompiler(
|
||||
|
||||
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? {
|
||||
return when (ref) {
|
||||
is ConstRef -> compileConst(ref.constValue)
|
||||
@ -1620,13 +1626,13 @@ class BytecodeCompiler(
|
||||
Pos.builtIn
|
||||
)
|
||||
val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null
|
||||
val fieldId = receiverClass.instanceFieldIdMap()[target.name] ?: -1
|
||||
val methodId = if (fieldId < 0) {
|
||||
receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name] ?: -1
|
||||
val fieldId = receiverClass.instanceFieldIdMap()[target.name]
|
||||
val methodId = if (fieldId == null) {
|
||||
receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name]
|
||||
} else {
|
||||
-1
|
||||
null
|
||||
}
|
||||
if (fieldId < 0 && methodId < 0) {
|
||||
if (fieldId == null && methodId == null) {
|
||||
val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
|
||||
?: throw BytecodeCompileException(
|
||||
"Unknown member ${target.name} on ${receiverClass.className}",
|
||||
@ -1660,8 +1666,10 @@ class BytecodeCompiler(
|
||||
}
|
||||
return value
|
||||
}
|
||||
val encodedFieldId = encodeMemberId(receiverClass, fieldId) ?: -1
|
||||
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: -1
|
||||
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 {
|
||||
val nullSlot = allocSlot()
|
||||
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||
@ -1672,7 +1680,7 @@ class BytecodeCompiler(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
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)
|
||||
}
|
||||
return value
|
||||
@ -2048,11 +2056,13 @@ class BytecodeCompiler(
|
||||
}
|
||||
val fieldId = receiverClass.instanceFieldIdMap()[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 dst = allocSlot()
|
||||
if (fieldId != null || methodId != null) {
|
||||
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 {
|
||||
val nullSlot = allocSlot()
|
||||
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||
@ -2064,7 +2074,7 @@ class BytecodeCompiler(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
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.mark(nullLabel)
|
||||
builder.emit(Opcode.CONST_NULL, dst)
|
||||
@ -2856,11 +2866,12 @@ class BytecodeCompiler(
|
||||
val dst = allocSlot()
|
||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
||||
if (methodId != null) {
|
||||
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId
|
||||
if (!ref.isOptional) {
|
||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
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)
|
||||
}
|
||||
val nullSlot = allocSlot()
|
||||
@ -2876,7 +2887,7 @@ class BytecodeCompiler(
|
||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
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.mark(nullLabel)
|
||||
builder.emit(Opcode.CONST_NULL, dst)
|
||||
@ -4659,43 +4670,17 @@ class BytecodeCompiler(
|
||||
?: resolveReceiverClass(ref.castValueRef())
|
||||
is FieldRef -> {
|
||||
val targetClass = resolveReceiverClass(ref.target) ?: return null
|
||||
if (targetClass == ObjString.type && ref.name == "re") {
|
||||
ObjRegex.type
|
||||
} else {
|
||||
null
|
||||
}
|
||||
inferFieldReturnClass(targetClass, ref.name)
|
||||
}
|
||||
is MethodCallRef -> {
|
||||
val targetClass = resolveReceiverClass(ref.receiver) ?: return null
|
||||
if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) {
|
||||
ObjRegex.type
|
||||
} else {
|
||||
when (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
|
||||
}
|
||||
inferMethodCallReturnClass(ref.name)
|
||||
}
|
||||
}
|
||||
is CallRef -> inferCallReturnClass(ref)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@ -4742,39 +4727,17 @@ class BytecodeCompiler(
|
||||
?: resolveReceiverClassForScopeCollection(ref.castValueRef())
|
||||
is FieldRef -> {
|
||||
val targetClass = resolveReceiverClassForScopeCollection(ref.target) ?: return null
|
||||
if (targetClass == ObjString.type && ref.name == "re") ObjRegex.type else null
|
||||
inferFieldReturnClass(targetClass, ref.name)
|
||||
}
|
||||
is MethodCallRef -> {
|
||||
val targetClass = resolveReceiverClassForScopeCollection(ref.receiver) ?: return null
|
||||
if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) {
|
||||
ObjRegex.type
|
||||
} else {
|
||||
when (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
|
||||
}
|
||||
inferMethodCallReturnClass(ref.name)
|
||||
}
|
||||
}
|
||||
is CallRef -> inferCallReturnClass(ref)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@ -4813,12 +4776,154 @@ class BytecodeCompiler(
|
||||
"Regex" -> ObjRegex.type
|
||||
"RegexMatch" -> ObjRegexMatch.type
|
||||
"MapEntry" -> ObjMapEntry.type
|
||||
"Instant" -> ObjInstant.type
|
||||
"DateTime" -> ObjDateTime.type
|
||||
"Duration" -> ObjDuration.type
|
||||
"Exception" -> ObjException.Root
|
||||
"Class" -> ObjClassType
|
||||
"Callable" -> Statement.type
|
||||
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) {
|
||||
for (cls in receiverClass.mro) {
|
||||
val name = extensionCallableName(cls.className, memberName)
|
||||
|
||||
@ -1321,7 +1321,8 @@ class CmdCallSlot(
|
||||
frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
|
||||
} else {
|
||||
// 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()) {
|
||||
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(
|
||||
internal val recvSlot: Int,
|
||||
internal val fieldId: Int,
|
||||
@ -1369,22 +1378,42 @@ class CmdGetMemberSlot(
|
||||
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)
|
||||
val cls = receiver as? ObjClass
|
||||
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
|
||||
val rec = fieldRec ?: run {
|
||||
if (methodId >= 0) {
|
||||
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
|
||||
if (methodIdResolved >= 0) {
|
||||
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
|
||||
} ?: frame.ensureScope().raiseSymbolNotFound("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) {
|
||||
val resolved = receiver.readField(frame.ensureScope(), name)
|
||||
frame.storeObjResult(dst, resolved.value)
|
||||
frame.storeObjResult(dst, autoCallIfMethod(resolved, receiver))
|
||||
return
|
||||
}
|
||||
val resolved = receiver.resolveRecord(frame.ensureScope(), rec, name, rec.declaringClass)
|
||||
frame.storeObjResult(dst, resolved.value)
|
||||
frame.storeObjResult(dst, autoCallIfMethod(resolved, receiver))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -1398,12 +1427,25 @@ class CmdSetMemberSlot(
|
||||
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)
|
||||
val cls = receiver as? ObjClass
|
||||
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
|
||||
val rec = fieldRec ?: run {
|
||||
if (methodId >= 0) {
|
||||
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
|
||||
if (methodIdResolved >= 0) {
|
||||
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
|
||||
} ?: frame.ensureScope().raiseSymbolNotFound("member")
|
||||
val name = rec.memberName ?: "<member>"
|
||||
@ -1429,8 +1471,14 @@ class CmdCallMemberSlot(
|
||||
}
|
||||
val receiver = frame.slotToObj(recvSlot)
|
||||
val inst = receiver as? ObjInstance
|
||||
val rec = inst?.methodRecordForId(methodId)
|
||||
?: receiver.objClass.methodRecordForId(methodId)
|
||||
val cls = receiver as? ObjClass
|
||||
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}")
|
||||
val callArgs = frame.buildArguments(argBase, argCount)
|
||||
val name = rec.memberName ?: "<member>"
|
||||
@ -1605,15 +1653,51 @@ class CmdFrame(
|
||||
}
|
||||
|
||||
private fun resolveModuleScope(scope: Scope): Scope {
|
||||
var current: Scope? = scope
|
||||
var last: Scope = scope
|
||||
while (current != null) {
|
||||
val moduleSlotName = fn.scopeSlotNames.indices
|
||||
.firstOrNull { fn.scopeSlotIsModule.getOrNull(it) == true }
|
||||
?.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.parent is ModuleScope) return current
|
||||
last = current
|
||||
current = current.parent
|
||||
current.parent?.let { queue.add(it) }
|
||||
if (current is ClosureScope) {
|
||||
queue.add(current.closureScope)
|
||||
} else if (current is ApplyScope) {
|
||||
queue.add(current.applied)
|
||||
}
|
||||
}
|
||||
return last
|
||||
return null
|
||||
}
|
||||
|
||||
fun ensureScope(): Scope {
|
||||
@ -1737,7 +1821,7 @@ class CmdFrame(
|
||||
scopeDepth -= 1
|
||||
}
|
||||
|
||||
fun getObj(slot: Int): Obj {
|
||||
suspend fun getObj(slot: Int): Obj {
|
||||
return if (slot < fn.scopeSlotCount) {
|
||||
getScopeSlotValue(slot)
|
||||
} else {
|
||||
@ -1755,7 +1839,7 @@ class CmdFrame(
|
||||
}
|
||||
}
|
||||
|
||||
fun getInt(slot: Int): Long {
|
||||
suspend fun getInt(slot: Int): Long {
|
||||
return if (slot < fn.scopeSlotCount) {
|
||||
getScopeSlotValue(slot).toLong()
|
||||
} else {
|
||||
@ -1786,7 +1870,7 @@ class CmdFrame(
|
||||
frame.setInt(local, value)
|
||||
}
|
||||
|
||||
fun getReal(slot: Int): Double {
|
||||
suspend fun getReal(slot: Int): Double {
|
||||
return if (slot < fn.scopeSlotCount) {
|
||||
getScopeSlotValue(slot).toDouble()
|
||||
} else {
|
||||
@ -1811,7 +1895,7 @@ class CmdFrame(
|
||||
}
|
||||
}
|
||||
|
||||
fun getBool(slot: Int): Boolean {
|
||||
suspend fun getBool(slot: Int): Boolean {
|
||||
return if (slot < fn.scopeSlotCount) {
|
||||
getScopeSlotValue(slot).toBool()
|
||||
} else {
|
||||
@ -1850,7 +1934,7 @@ class CmdFrame(
|
||||
addrScopeSlots[addrSlot] = scopeSlot
|
||||
}
|
||||
|
||||
fun getAddrObj(addrSlot: Int): Obj {
|
||||
suspend fun getAddrObj(addrSlot: Int): Obj {
|
||||
return getScopeSlotValueAtAddr(addrSlot)
|
||||
}
|
||||
|
||||
@ -1858,7 +1942,7 @@ class CmdFrame(
|
||||
setScopeSlotValueAtAddr(addrSlot, value)
|
||||
}
|
||||
|
||||
fun getAddrInt(addrSlot: Int): Long {
|
||||
suspend fun getAddrInt(addrSlot: Int): Long {
|
||||
return getScopeSlotValueAtAddr(addrSlot).toLong()
|
||||
}
|
||||
|
||||
@ -1866,7 +1950,7 @@ class CmdFrame(
|
||||
setScopeSlotValueAtAddr(addrSlot, ObjInt.of(value))
|
||||
}
|
||||
|
||||
fun getAddrReal(addrSlot: Int): Double {
|
||||
suspend fun getAddrReal(addrSlot: Int): Double {
|
||||
return getScopeSlotValueAtAddr(addrSlot).toDouble()
|
||||
}
|
||||
|
||||
@ -1874,7 +1958,7 @@ class CmdFrame(
|
||||
setScopeSlotValueAtAddr(addrSlot, ObjReal.of(value))
|
||||
}
|
||||
|
||||
fun getAddrBool(addrSlot: Int): Boolean {
|
||||
suspend fun getAddrBool(addrSlot: Int): Boolean {
|
||||
return getScopeSlotValueAtAddr(addrSlot).toBool()
|
||||
}
|
||||
|
||||
@ -1882,11 +1966,18 @@ class CmdFrame(
|
||||
setScopeSlotValueAtAddr(addrSlot, if (value) ObjTrue else ObjFalse)
|
||||
}
|
||||
|
||||
fun slotToObj(slot: Int): Obj {
|
||||
suspend fun slotToObj(slot: Int): Obj {
|
||||
if (slot < fn.scopeSlotCount) {
|
||||
return getScopeSlotValue(slot)
|
||||
}
|
||||
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)) {
|
||||
SlotType.INT.code -> ObjInt.of(frame.getInt(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 index = ensureScopeSlot(target, slot)
|
||||
val record = target.getSlotRecord(index)
|
||||
val direct = record.value
|
||||
if (direct is FrameSlotRef) return direct.read()
|
||||
if (direct !== ObjUnset) return direct
|
||||
val name = fn.scopeSlotNames[slot] ?: return record.value
|
||||
val name = fn.scopeSlotNames[slot]
|
||||
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
|
||||
if (resolved.value !== ObjUnset) {
|
||||
target.updateSlotFor(name, resolved)
|
||||
@ -2082,15 +2179,21 @@ class CmdFrame(
|
||||
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 index = addrIndices[addrSlot]
|
||||
val record = target.getSlotRecord(index)
|
||||
val direct = record.value
|
||||
if (direct is FrameSlotRef) return direct.read()
|
||||
if (direct !== ObjUnset) return direct
|
||||
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
|
||||
if (resolved.value !== ObjUnset) {
|
||||
target.updateSlotFor(name, resolved)
|
||||
|
||||
@ -65,7 +65,8 @@ open class Obj {
|
||||
fun isInstanceOf(someClass: Obj) = someClass === objClass ||
|
||||
objClass.allParentsSet.contains(someClass) ||
|
||||
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) =
|
||||
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()
|
||||
?: 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(
|
||||
"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") {
|
||||
val wrapper = object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
override suspend fun execute(s: Scope): Obj {
|
||||
val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj
|
||||
val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray()
|
||||
@ -587,16 +606,19 @@ open class Obj {
|
||||
scope.raiseNotImplemented()
|
||||
}
|
||||
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj =
|
||||
if (PerfFlags.SCOPE_POOL)
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj {
|
||||
val usePool = PerfFlags.SCOPE_POOL && this !is Statement
|
||||
return if (usePool) {
|
||||
scope.withChildFrame(args, newThisObj = thisObj) { child ->
|
||||
if (declaringClass != null) child.currentClassCtx = declaringClass
|
||||
callOn(child)
|
||||
}
|
||||
else
|
||||
} else {
|
||||
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
|
||||
if (declaringClass != null) it.currentClassCtx = declaringClass
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
||||
callOn(
|
||||
|
||||
@ -92,34 +92,48 @@ open class ObjException(
|
||||
|
||||
|
||||
companion object {
|
||||
private var stackTraceCaptureDepth = 0
|
||||
|
||||
suspend fun captureStackTrace(scope: Scope): 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 lastPos: Pos? = null
|
||||
while (s != null) {
|
||||
val pos = s.pos
|
||||
if (pos != lastPos && !pos.currentLine.isEmpty()) {
|
||||
if( (lastPos == null || (lastPos.source != pos.source || lastPos.line != pos.line)) ) {
|
||||
if (maybeCls != null) {
|
||||
result.list += maybeCls.callWithArgs(
|
||||
scope,
|
||||
pos.source.objSourceName,
|
||||
ObjInt(pos.line.toLong()),
|
||||
ObjInt(pos.column.toLong()),
|
||||
ObjString(pos.currentLine)
|
||||
)
|
||||
} else {
|
||||
// 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}")
|
||||
try {
|
||||
while (s != null) {
|
||||
val pos = s.pos
|
||||
if (pos != lastPos && !pos.currentLine.isEmpty()) {
|
||||
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) {
|
||||
try {
|
||||
result.list += maybeCls.callWithArgs(
|
||||
scope,
|
||||
pos.source.objSourceName,
|
||||
ObjInt(pos.line.toLong()),
|
||||
ObjInt(pos.column.toLong()),
|
||||
ObjString(pos.currentLine)
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
// Fallback textual entry if StackTraceEntry fails to instantiate
|
||||
result.list += fallback
|
||||
}
|
||||
} else {
|
||||
// Fallback textual entry if StackTraceEntry class is not available in this scope
|
||||
result.list += fallback
|
||||
}
|
||||
lastPos = pos
|
||||
}
|
||||
lastPos = pos
|
||||
}
|
||||
s = s.parent
|
||||
}
|
||||
s = s.parent
|
||||
return result
|
||||
} finally {
|
||||
stackTraceCaptureDepth -= 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
|
||||
|
||||
@ -174,6 +174,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") {
|
||||
val wrapper = object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
override suspend fun execute(s: Scope): Obj {
|
||||
val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj
|
||||
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
|
||||
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 ->
|
||||
methodRecordForKey(key)?.let { rec ->
|
||||
if (rec.visibility == Visibility.Public && !rec.isAbstract) {
|
||||
|
||||
@ -286,5 +286,3 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -445,38 +445,64 @@ class CastRef(
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val v0 = valueRef.evalValue(scope)
|
||||
val t = typeRef.evalValue(scope)
|
||||
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${t} is not the class instance")
|
||||
// unwrap qualified views
|
||||
val v = when (v0) {
|
||||
is ObjQualifiedView -> v0.instance
|
||||
else -> v0
|
||||
}
|
||||
return if (v.isInstanceOf(target)) {
|
||||
// For instances, return a qualified view to enforce ancestor-start dispatch
|
||||
if (v is ObjInstance) ObjQualifiedView(v, target).asReadonly else v.asReadonly
|
||||
} else {
|
||||
if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError(
|
||||
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${target.className}"
|
||||
)
|
||||
return when (t) {
|
||||
is ObjClass -> {
|
||||
if (v.isInstanceOf(t)) {
|
||||
// For instances, return a qualified view to enforce ancestor-start dispatch
|
||||
if (v is ObjInstance) ObjQualifiedView(v, t).asReadonly else v.asReadonly
|
||||
} else {
|
||||
if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError(
|
||||
"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 {
|
||||
val v0 = valueRef.evalValue(scope)
|
||||
val t = typeRef.evalValue(scope)
|
||||
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${t} is not the class instance")
|
||||
// unwrap qualified views
|
||||
val v = when (v0) {
|
||||
is ObjQualifiedView -> v0.instance
|
||||
else -> v0
|
||||
}
|
||||
return if (v.isInstanceOf(target)) {
|
||||
// For instances, return a qualified view to enforce ancestor-start dispatch
|
||||
if (v is ObjInstance) ObjQualifiedView(v, target) else v
|
||||
} else {
|
||||
if (isNullable) ObjNull else scope.raiseClassCastError(
|
||||
"Cannot cast ${(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${target.className}"
|
||||
)
|
||||
return when (t) {
|
||||
is ObjClass -> {
|
||||
if (v.isInstanceOf(t)) {
|
||||
// For instances, return a qualified view to enforce ancestor-start dispatch
|
||||
if (v is ObjInstance) ObjQualifiedView(v, t) else v
|
||||
} else {
|
||||
if (isNullable) ObjNull else scope.raiseClassCastError(
|
||||
"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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1175,6 +1201,10 @@ class FieldRef(
|
||||
if (rec.receiver != null && rec.declaringClass != null) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -2300,6 +2330,11 @@ class LocalSlotRef(
|
||||
scope.raiseError("slot index out of range for $name")
|
||||
}
|
||||
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)) {
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
|
||||
}
|
||||
@ -2324,6 +2359,11 @@ class LocalSlotRef(
|
||||
scope.raiseError("slot index out of range for $name")
|
||||
}
|
||||
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)) {
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
|
||||
}
|
||||
|
||||
@ -68,11 +68,17 @@ abstract class ImportProvider(
|
||||
|
||||
suspend fun newStdScope(pos: Pos = Pos.builtIn): Scope =
|
||||
cachedStdScope.get {
|
||||
newModuleAt(pos).also {
|
||||
it.eval("import lyng.stdlib\n")
|
||||
val module = newModuleAt(pos)
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user