Compare commits

..

No commits in common. "ab39110834fd3a41919827a1cf73433095fc7369" and "a1ea09440d5837f2ea0b27cc10dcffda4d8eb2a6" have entirely different histories.

10 changed files with 56 additions and 607 deletions

View File

@ -1,137 +0,0 @@
package net.sergeych
import net.sergeych.lyng.EvalSession
import net.sergeych.lyng.Source
import net.sergeych.lyng.obj.ObjString
import kotlinx.coroutines.runBlocking
import java.nio.file.Files
import kotlin.io.path.writeText
import kotlin.test.Test
import kotlin.test.assertEquals
class CliLocalModuleImportRegressionJvmTest {
private fun writeTransitiveImportTree(root: java.nio.file.Path) {
val packageDir = Files.createDirectories(root.resolve("package1"))
val nestedDir = Files.createDirectories(packageDir.resolve("nested"))
packageDir.resolve("alpha.lyng").writeText(
"""
package package1.alpha
import lyng.stdlib
import lyng.io.net
class Alpha {
val headers = Map<String, String>()
fun makeTask(port: Int, host: String): Deferred = launch {
host + ":" + port
}
fun netModule() = Net
}
fun alphaValue() = "alpha"
""".trimIndent()
)
packageDir.resolve("beta.lyng").writeText(
"""
package package1.beta
import lyng.stdlib
import package1.alpha
fun betaValue() = alphaValue() + "|beta"
""".trimIndent()
)
nestedDir.resolve("gamma.lyng").writeText(
"""
package package1.nested.gamma
import lyng.io.net
import package1.alpha
import package1.beta
val String.gammaTag get() = this + "|gamma"
fun gammaValue() = betaValue().gammaTag
fun netModule() = Net
""".trimIndent()
)
packageDir.resolve("entry.lyng").writeText(
"""
package package1.entry
import lyng.stdlib
import lyng.io.net
import package1.alpha
import package1.beta
import package1.nested.gamma
fun report() = gammaValue() + "|entry"
""".trimIndent()
)
}
@Test
fun localModuleUsingLaunchAndNetImportsWithoutStdlibRedefinition() = runBlocking {
val root = Files.createTempDirectory("lyng-cli-import-regression")
try {
val mainFile = root.resolve("main.lyng")
writeTransitiveImportTree(root)
mainFile.writeText(
"""
import package1.entry
import package1.beta
import package1.nested.gamma
println(report())
""".trimIndent()
)
executeFile(mainFile.toString(), emptyList())
} finally {
root.toFile().deleteRecursively()
}
}
@Test
fun localModuleImportsAreNoOpsWhenEvaldRepeatedlyOnSameCliContext() = runBlocking {
val root = Files.createTempDirectory("lyng-cli-import-regression-repeat")
try {
val mainFile = root.resolve("main.lyng")
writeTransitiveImportTree(root)
mainFile.writeText("println(\"bootstrap\")")
val session = EvalSession(newCliScope(emptyList(), mainFile.toString()))
try {
repeat(5) { index ->
val result = evalOnCliDispatcher(
session,
Source(
"<repeat-local-import-$index>",
"""
import package1.entry
import package1.nested.gamma
import package1.beta
import package1.alpha
report()
""".trimIndent()
)
) as ObjString
assertEquals(
"alpha|beta|gamma|entry",
result.value
)
}
} finally {
session.cancelAndJoin()
}
} finally {
root.toFile().deleteRecursively()
}
}
}

View File

@ -64,73 +64,6 @@ class CliLocalImportsJvmTest {
return CliResult(outBuf.toString("UTF-8"), errBuf.toString("UTF-8"), exitCode) return CliResult(outBuf.toString("UTF-8"), errBuf.toString("UTF-8"), exitCode)
} }
private fun writeTransitiveImportTree(root: java.nio.file.Path) {
val packageDir = Files.createDirectories(root.resolve("package1"))
val nestedDir = Files.createDirectories(packageDir.resolve("nested"))
Files.writeString(
packageDir.resolve("alpha.lyng"),
"""
package package1.alpha
import lyng.stdlib
import lyng.io.net
class Alpha {
val headers = Map<String, String>()
fun makeTask(port: Int, host: String): Deferred = launch {
host + ":" + port
}
fun netModule() = Net
}
fun alphaValue() = "alpha"
""".trimIndent()
)
Files.writeString(
packageDir.resolve("beta.lyng"),
"""
package package1.beta
import lyng.stdlib
import package1.alpha
fun betaValue() = alphaValue() + "|beta"
""".trimIndent()
)
Files.writeString(
nestedDir.resolve("gamma.lyng"),
"""
package package1.nested.gamma
import lyng.io.net
import package1.alpha
import package1.beta
val String.gammaTag get() = this + "|gamma"
fun gammaValue() = betaValue().gammaTag
fun netModule() = Net
""".trimIndent()
)
Files.writeString(
packageDir.resolve("entry.lyng"),
"""
package package1.entry
import lyng.stdlib
import lyng.io.net
import package1.alpha
import package1.beta
import package1.nested.gamma
fun report() = gammaValue() + "|entry"
""".trimIndent()
)
}
@Test @Test
fun cliDiscoversSiblingAndNestedLocalImportsFromEntryRoot() { fun cliDiscoversSiblingAndNestedLocalImportsFromEntryRoot() {
val dir = Files.createTempDirectory("lyng_cli_local_imports_") val dir = Files.createTempDirectory("lyng_cli_local_imports_")
@ -201,37 +134,4 @@ class CliLocalImportsJvmTest {
dir.toFile().deleteRecursively() dir.toFile().deleteRecursively()
} }
} }
@Test
fun cliHandlesOverlappingDirectoryImportsWithTransitiveStdlibAndNetSymbols() {
val dir = Files.createTempDirectory("lyng_cli_local_imports_transitive_")
try {
val mainFile = dir.resolve("main.lyng")
writeTransitiveImportTree(dir)
Files.writeString(
mainFile,
"""
import package1.entry
import package1.beta
import package1.nested.gamma
println(report())
println(gammaValue())
""".trimIndent()
)
val result = runCli(mainFile.toString())
assertTrue(result.err, result.err.isBlank())
assertTrue(
result.out,
result.out.contains("alpha|beta|gamma|entry")
)
assertTrue(
result.out,
result.out.contains("alpha|beta|gamma")
)
} finally {
dir.toFile().deleteRecursively()
}
}
} }

View File

@ -3436,12 +3436,12 @@ class Compiler(
val resolvedRecords = ArrayList<ObjRecord>(captureSlots.size) val resolvedRecords = ArrayList<ObjRecord>(captureSlots.size)
val resolvedNames = ArrayList<String>(captureSlots.size) val resolvedNames = ArrayList<String>(captureSlots.size)
for (capture in captureSlots) { for (capture in captureSlots) {
val rec = resolveStableCaptureRecord( val rec = closureScope.chainLookupIgnoreClosure(
closureScope,
capture.name, capture.name,
context.currentClassCtx followClosure = true,
caller = context.currentClassCtx
) ?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found") ) ?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found")
resolvedRecords.add(freezeImmutableCaptureRecord(rec)) resolvedRecords.add(rec)
resolvedNames.add(capture.name) resolvedNames.add(capture.name)
} }
context.captureRecords = resolvedRecords context.captureRecords = resolvedRecords
@ -8766,46 +8766,6 @@ class Compiler(
} }
} }
private fun freezeImmutableCaptureRecord(record: ObjRecord): ObjRecord {
val value = record.value as Obj?
if (record.isMutable || record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || value is ObjProperty) {
return record
}
return when (value) {
is FrameSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
is RecordSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
is ScopeSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
null -> record
else -> record.copy()
}
}
private fun isTransientCapturePlaceholder(value: Obj?): Boolean {
return when (value) {
null, ObjVoid -> true
is FrameSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
is RecordSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
is ScopeSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
else -> false
}
}
private fun resolveStableCaptureRecord(scope: Scope, name: String, caller: ObjClass?): ObjRecord? {
val direct = scope.chainLookupIgnoreClosure(name, followClosure = true, caller = caller) ?: scope.get(name)
if (direct != null && !isTransientCapturePlaceholder(direct.value as Obj?)) {
return direct
}
var parent = scope.parent
while (parent != null) {
val candidate = parent.chainLookupIgnoreClosure(name, followClosure = true, caller = caller) ?: parent.get(name)
if (candidate != null && !isTransientCapturePlaceholder(candidate.value as Obj?)) {
return candidate
}
parent = parent.parent
}
return direct
}
private suspend fun parseFunctionDeclaration( private suspend fun parseFunctionDeclaration(
visibility: Visibility = Visibility.Public, visibility: Visibility = Visibility.Public,
isAbstract: Boolean = false, isAbstract: Boolean = false,
@ -9187,12 +9147,12 @@ class Compiler(
} else if (captureBase != null && captureNames.isNotEmpty()) { } else if (captureBase != null && captureNames.isNotEmpty()) {
val resolvedRecords = ArrayList<ObjRecord>(captureNames.size) val resolvedRecords = ArrayList<ObjRecord>(captureNames.size)
for (name in captureNames) { for (name in captureNames) {
val rec = resolveStableCaptureRecord( val rec = captureBase.chainLookupIgnoreClosure(
captureBase,
name, name,
context.currentClassCtx followClosure = true,
caller = context.currentClassCtx
) ?: captureBase.raiseSymbolNotFound("symbol $name not found") ) ?: captureBase.raiseSymbolNotFound("symbol $name not found")
resolvedRecords.add(freezeImmutableCaptureRecord(rec)) resolvedRecords.add(rec)
} }
context.captureRecords = resolvedRecords context.captureRecords = resolvedRecords
context.captureNames = captureNames context.captureNames = captureNames
@ -9233,12 +9193,7 @@ class Compiler(
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) { val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
context.resolve(record, localName) context.resolve(record, localName)
} else { } else {
when (val direct = record.value) { record.value
is FrameSlotRef -> direct.read()
is RecordSlotRef -> direct.read(context, localName)
is ScopeSlotRef -> direct.read()
else -> direct
}
} }
frame.frame.setObj(i, value) frame.frame.setObj(i, value)
} }

View File

@ -79,13 +79,6 @@ class FrameSlotRef(
} }
} }
internal fun resolvedCaptureValueOrNull(): Obj? {
return when (frame.getSlotTypeCode(slot)) {
SlotType.INT.code, SlotType.REAL.code, SlotType.BOOL.code -> read()
else -> peekValue()?.let { read() }
}
}
fun write(value: Obj) { fun write(value: Obj) {
when (value) { when (value) {
is ObjInt -> frame.setInt(slot, value.value) is ObjInt -> frame.setInt(slot, value.value)
@ -139,16 +132,6 @@ class ScopeSlotRef(
} }
} }
internal fun resolvedCaptureValueOrNull(): Obj? {
val record = scope.getSlotRecord(slot)
return when (val direct = record.value as Obj?) {
is FrameSlotRef -> direct.resolvedCaptureValueOrNull()
is RecordSlotRef -> direct.resolvedCaptureValueOrNull()
is ScopeSlotRef -> direct.resolvedCaptureValueOrNull()
else -> direct
}
}
fun write(value: Obj) { fun write(value: Obj) {
scope.setSlotValue(slot, value) scope.setSlotValue(slot, value)
} }
@ -215,15 +198,6 @@ class RecordSlotRef(
} }
} }
internal fun resolvedCaptureValueOrNull(): Obj? {
return when (val direct = record.value as Obj?) {
is FrameSlotRef -> direct.resolvedCaptureValueOrNull()
is RecordSlotRef -> direct.resolvedCaptureValueOrNull()
is ScopeSlotRef -> direct.resolvedCaptureValueOrNull()
else -> direct
}
}
fun write(value: Obj) { fun write(value: Obj) {
val direct = record.value val direct = record.value
if (direct is ScopeSlotRef) { if (direct is ScopeSlotRef) {

View File

@ -32,7 +32,6 @@ class ModuleScope(
pos: Pos = Pos.builtIn, pos: Pos = Pos.builtIn,
override val packageName: String override val packageName: String
) : Scope(importProvider.rootScope, Arguments.EMPTY, pos) { ) : Scope(importProvider.rootScope, Arguments.EMPTY, pos) {
private fun ObjRecord.importedCopy(source: Scope): ObjRecord = copy(importedFrom = source)
constructor(importProvider: ImportProvider, source: Source) : this(importProvider, source.startPos, source.fileName) constructor(importProvider: ImportProvider, source: Source) : this(importProvider, source.startPos, source.fileName)
@ -85,7 +84,7 @@ class ModuleScope(
override suspend fun importInto(scope: Scope, symbols: Map<String, String>?) { override suspend fun importInto(scope: Scope, symbols: Map<String, String>?) {
val symbolsToImport = symbols?.keys?.toMutableSet() val symbolsToImport = symbols?.keys?.toMutableSet()
for ((symbol, record) in this.objects) { for ((symbol, record) in this.objects) {
if (record.visibility.isPublic && record.importedFrom == null) { if (record.visibility.isPublic) {
val newName = symbols?.let { ss: Map<String, String> -> val newName = symbols?.let { ss: Map<String, String> ->
ss[symbol] ss[symbol]
?.also { symbolsToImport!!.remove(symbol) } ?.also { symbolsToImport!!.remove(symbol) }
@ -95,21 +94,21 @@ class ModuleScope(
if (newName != null) { if (newName != null) {
val existing = scope.objects[newName] val existing = scope.objects[newName]
if (existing != null) { if (existing != null) {
val sameBinding = existing.importedFrom == this if (existing.importedFrom != record.importedFrom)
if (!sameBinding)
scope.raiseError("symbol ${existing.importedFrom?.packageName}.$newName already exists, redefinition on import is not allowed") scope.raiseError("symbol ${existing.importedFrom?.packageName}.$newName already exists, redefinition on import is not allowed")
// already imported // already imported
} else { } else {
val imported = record.importedCopy(this) // when importing records, we keep track of its package (not otherwise needed)
scope.objects[newName] = imported if (record.importedFrom == null) record.importedFrom = this
scope.updateSlotFor(newName, imported) scope.objects[newName] = record
scope.updateSlotFor(newName, record)
} }
} }
} }
} }
for ((cls, map) in this.extensions) { for ((cls, map) in this.extensions) {
for ((symbol, record) in map) { for ((symbol, record) in map) {
if (record.visibility.isPublic && record.importedFrom == null) { if (record.visibility.isPublic) {
val newName = symbols?.let { ss: Map<String, String> -> val newName = symbols?.let { ss: Map<String, String> ->
ss[symbol] ss[symbol]
?.also { symbolsToImport!!.remove(symbol) } ?.also { symbolsToImport!!.remove(symbol) }
@ -117,7 +116,7 @@ class ModuleScope(
} ?: if (symbols == null) symbol else null } ?: if (symbols == null) symbol else null
if (newName != null) { if (newName != null) {
scope.addExtension(cls, newName, record.importedCopy(this)) scope.addExtension(cls, newName, record)
} }
} }
} }

View File

@ -181,42 +181,37 @@ class Script(
} }
} }
private fun importedBindingRecord(record: ObjRecord, source: Scope): ObjRecord =
record.copy(importedFrom = source)
private suspend fun seedImportBindings(scope: Scope, seedScope: Scope) { private suspend fun seedImportBindings(scope: Scope, seedScope: Scope) {
val provider = scope.currentImportProvider val provider = scope.currentImportProvider
val importedModules = LinkedHashSet<ModuleScope>() val importedModules = LinkedHashSet<ModuleScope>()
for (moduleRef in this.importedModules) { for (moduleRef in this.importedModules) {
importedModules.add(provider.prepareImport(moduleRef.pos, moduleRef.name, null)) importedModules.add(provider.prepareImport(moduleRef.pos, moduleRef.name, null))
} }
if (scope is ModuleScope) {
scope.importedModules = importedModules.toList()
}
for (module in importedModules) { for (module in importedModules) {
module.importInto(scope, null) module.importInto(scope, null)
} }
for ((name, binding) in importBindings) { for ((name, binding) in importBindings) {
val sourceScope: Scope val record = when (val source = binding.source) {
val baseRecord = 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) importedModules.add(module)
sourceScope = 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")
} }
ImportBindingSource.Root -> { ImportBindingSource.Root -> {
sourceScope = provider.rootScope
provider.rootScope.objects[binding.symbol]?.takeIf { it.visibility.isPublic } provider.rootScope.objects[binding.symbol]?.takeIf { it.visibility.isPublic }
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found") ?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
} }
ImportBindingSource.Seed -> { ImportBindingSource.Seed -> {
sourceScope = seedScope
findSeedRecord(seedScope, binding.symbol) findSeedRecord(seedScope, binding.symbol)
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found") ?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
} }
} }
val record = importedBindingRecord(baseRecord, sourceScope)
if (name == "Exception" && record.value !is ObjClass) { if (name == "Exception" && record.value !is ObjClass) {
scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false, importedFrom = sourceScope)) scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false))
} else { } else {
scope.updateSlotFor(name, record) scope.updateSlotFor(name, record)
} }
@ -224,15 +219,12 @@ class Script(
for (module in importedModules) { for (module in importedModules) {
for ((cls, map) in module.extensions) { for ((cls, map) in module.extensions) {
for ((symbol, record) in map) { for ((symbol, record) in map) {
if (record.visibility.isPublic && record.importedFrom == null) { if (record.visibility.isPublic) {
scope.addExtension(cls, symbol, importedBindingRecord(record, module)) scope.addExtension(cls, symbol, record)
} }
} }
} }
} }
if (scope is ModuleScope) {
scope.importedModules = importedModules.toList()
}
} }
private fun findSeedRecord(scope: Scope?, name: String): ObjRecord? { private fun findSeedRecord(scope: Scope?, name: String): ObjRecord? {

View File

@ -53,12 +53,7 @@ class BytecodeStatement private constructor(
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) { val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) {
scope.resolve(record, name) scope.resolve(record, name)
} else { } else {
when (val direct = record.value) { record.value
is FrameSlotRef -> direct.read()
is RecordSlotRef -> direct.read(scope, name)
is ScopeSlotRef -> direct.read()
else -> direct
}
} }
frame.frame.setObj(i, value) frame.frame.setObj(i, value)
} }

View File

@ -2560,46 +2560,6 @@ private fun captureNamesForStatement(stmt: Statement?): List<String> {
return ordered.toList() return ordered.toList()
} }
private fun freezeImmutableCaptureRecord(record: ObjRecord): ObjRecord {
val value = record.value as Obj?
if (record.isMutable || record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || value is ObjProperty) {
return record
}
return when (value) {
is FrameSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
is RecordSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
is ScopeSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
null -> record
else -> record.copy()
}
}
private fun isTransientCapturePlaceholder(value: Obj?): Boolean {
return when (value) {
null, ObjVoid -> true
is FrameSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
is RecordSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
is ScopeSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
else -> false
}
}
private fun resolveStableCaptureRecord(scope: Scope, name: String): ObjRecord? {
val direct = scope.chainLookupIgnoreClosure(name, followClosure = true) ?: scope.get(name)
if (direct != null && !isTransientCapturePlaceholder(direct.value as Obj?)) {
return direct
}
var parent = scope.parent
while (parent != null) {
val candidate = parent.chainLookupIgnoreClosure(name, followClosure = true) ?: parent.get(name)
if (candidate != null && !isTransientCapturePlaceholder(candidate.value as Obj?)) {
return candidate
}
parent = parent.parent
}
return direct
}
private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<String>): List<ObjRecord>? { private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<String>): List<ObjRecord>? {
if (captureNames.isEmpty()) return null if (captureNames.isEmpty()) return null
val records = ArrayList<ObjRecord>(captureNames.size) val records = ArrayList<ObjRecord>(captureNames.size)
@ -2615,16 +2575,12 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
} }
} else { } else {
val raw = frame.frame.getRawObj(localIndex) val raw = frame.frame.getRawObj(localIndex)
val captureRecord = if (isTransientCapturePlaceholder(raw)) { val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name)
resolveStableCaptureRecord(frame.scope.parent ?: frame.scope, name) if (scoped != null && scoped.value !== ObjUnset) {
} else { records += scoped
resolveStableCaptureRecord(frame.scope, name)
}
if (captureRecord != null) {
records += freezeImmutableCaptureRecord(captureRecord)
continue continue
} }
records += freezeImmutableCaptureRecord(ObjRecord(FrameSlotRef(frame.frame, localIndex), isMutable)) records += ObjRecord(FrameSlotRef(frame.frame, localIndex), isMutable)
} }
continue continue
} }
@ -2632,7 +2588,7 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
if (scopeSlot >= 0) { if (scopeSlot >= 0) {
val target = frame.scopeTarget(scopeSlot) val target = frame.scopeTarget(scopeSlot)
val index = frame.fn.scopeSlotIndices[scopeSlot] val index = frame.fn.scopeSlotIndices[scopeSlot]
records += freezeImmutableCaptureRecord(target.getSlotRecord(index)) records += target.getSlotRecord(index)
continue continue
} }
val scopeCaptures = frame.scope.captureRecords val scopeCaptures = frame.scope.captureRecords
@ -2649,7 +2605,7 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
} }
val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name) val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name)
if (scoped != null) { if (scoped != null) {
records += freezeImmutableCaptureRecord(scoped) records += scoped
continue continue
} }
frame.ensureScope().raiseSymbolNotFound("capture $name not found") frame.ensureScope().raiseSymbolNotFound("capture $name not found")
@ -3421,12 +3377,7 @@ class CmdGetMemberSlot(
resolved.declaringClass resolved.declaringClass
) )
} else { } else {
when (val value = resolved.value) { resolved.value
is FrameSlotRef -> value.read()
is RecordSlotRef -> value.read(frame.ensureScope(), name)
is ScopeSlotRef -> value.read()
else -> value
}
} }
} }
if (receiver is ObjQualifiedView) { if (receiver is ObjQualifiedView) {
@ -3833,15 +3784,13 @@ class BytecodeLambdaCallable(
override val pos: Pos, override val pos: Pos,
) : Statement(), BytecodeCallable { ) : Statement(), BytecodeCallable {
private fun freezeRecord(record: ObjRecord): ObjRecord { private fun freezeRecord(record: ObjRecord): ObjRecord {
if (record.isMutable) return record val frozenValue = when (val raw = record.value) {
val raw = record.value as Obj? is net.sergeych.lyng.FrameSlotRef -> raw.read()
return when (raw) { is net.sergeych.lyng.RecordSlotRef -> raw.read()
is net.sergeych.lyng.FrameSlotRef -> raw.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record is net.sergeych.lyng.ScopeSlotRef -> raw.read()
is net.sergeych.lyng.RecordSlotRef -> raw.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record else -> raw
is net.sergeych.lyng.ScopeSlotRef -> raw.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
null -> record
else -> record.copy()
} }
return record.copy(value = frozenValue)
} }
private fun resolveCaptureRecords(base: Scope): List<ObjRecord>? { private fun resolveCaptureRecords(base: Scope): List<ObjRecord>? {
@ -3859,7 +3808,7 @@ class BytecodeLambdaCallable(
return BytecodeLambdaCallable( return BytecodeLambdaCallable(
fn = fn, fn = fn,
closureScope = newClosureScope, closureScope = newClosureScope,
captureRecords = captureRecords ?: resolveCaptureRecords(newClosureScope), captureRecords = resolveCaptureRecords(newClosureScope) ?: captureRecords,
captureNames = captureNames, captureNames = captureNames,
paramSlotPlan = paramSlotPlan, paramSlotPlan = paramSlotPlan,
argsDeclaration = argsDeclaration, argsDeclaration = argsDeclaration,
@ -3974,12 +3923,7 @@ class BytecodeLambdaCallable(
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) { if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
context.resolve(record, name) context.resolve(record, name)
} else { } else {
when (val direct = record.value) { record.value
is FrameSlotRef -> direct.read()
is RecordSlotRef -> direct.read(context, name)
is ScopeSlotRef -> direct.read()
else -> direct
}
} }
frame.frame.setObj(i, value) frame.frame.setObj(i, value)
} }
@ -4146,31 +4090,7 @@ class CmdFrame(
frame.setObj(localIndex, record.delegate ?: ObjNull) frame.setObj(localIndex, record.delegate ?: ObjNull)
} else { } else {
val value = record.value val value = record.value
if (!record.isMutable && value is FrameSlotRef) { if (value is FrameSlotRef) {
val resolved = value.peekValue()
if (resolved != null) {
if (value.refersTo(frame, localIndex)) continue
frame.setObj(localIndex, value.read())
} else {
frame.setObj(localIndex, value)
}
} else if (!record.isMutable && value is RecordSlotRef) {
val resolved = value.peekValue()
if (resolved != null) {
frame.setObj(localIndex, value.read())
} else {
frame.setObj(localIndex, value)
}
} else if (!record.isMutable && value is ScopeSlotRef) {
val resolved = value.peekValue()
if (resolved != null) {
frame.setObj(localIndex, value.read())
} else {
frame.setObj(localIndex, value)
}
} else if (!record.isMutable) {
frame.setObj(localIndex, value)
} else if (value is FrameSlotRef) {
if (value.refersTo(frame, localIndex)) continue if (value.refersTo(frame, localIndex)) continue
frame.setObj(localIndex, value) frame.setObj(localIndex, value)
} else { } else {
@ -4185,30 +4105,7 @@ class CmdFrame(
frame.setObj(idx, record.delegate ?: ObjNull) frame.setObj(idx, record.delegate ?: ObjNull)
} else { } else {
val value = record.value val value = record.value
if (!record.isMutable && value is FrameSlotRef) { if (value is FrameSlotRef) {
val resolved = value.peekValue()
if (resolved != null) {
frame.setObj(idx, value.read())
} else {
frame.setObj(idx, value)
}
} else if (!record.isMutable && value is RecordSlotRef) {
val resolved = value.peekValue()
if (resolved != null) {
frame.setObj(idx, value.read())
} else {
frame.setObj(idx, value)
}
} else if (!record.isMutable && value is ScopeSlotRef) {
val resolved = value.peekValue()
if (resolved != null) {
frame.setObj(idx, value.read())
} else {
frame.setObj(idx, value)
}
} else if (!record.isMutable) {
frame.setObj(idx, value)
} else if (value is FrameSlotRef) {
frame.setObj(idx, value) frame.setObj(idx, value)
} else { } else {
frame.setObj(idx, RecordSlotRef(record)) frame.setObj(idx, RecordSlotRef(record))
@ -5187,7 +5084,6 @@ class CmdFrame(
when (obj) { when (obj) {
is FrameSlotRef -> obj.read() is FrameSlotRef -> obj.read()
is RecordSlotRef -> obj.read(scope, localName) is RecordSlotRef -> obj.read(scope, localName)
is ScopeSlotRef -> obj.read()
is ObjProperty -> resolvePropertyLikeLocal(localName, obj) is ObjProperty -> resolvePropertyLikeLocal(localName, obj)
ObjUnset -> resolveUnsetLocal(localName) ObjUnset -> resolveUnsetLocal(localName)
else -> obj else -> obj
@ -5199,7 +5095,6 @@ class CmdFrame(
when (obj) { when (obj) {
is FrameSlotRef -> obj.read() is FrameSlotRef -> obj.read()
is RecordSlotRef -> obj.read(scope, localName) is RecordSlotRef -> obj.read(scope, localName)
is ScopeSlotRef -> obj.read()
is ObjProperty -> resolvePropertyLikeLocal(localName, obj) is ObjProperty -> resolvePropertyLikeLocal(localName, obj)
ObjUnset -> resolveUnsetLocal(localName) ObjUnset -> resolveUnsetLocal(localName)
else -> obj else -> obj
@ -5224,24 +5119,7 @@ class CmdFrame(
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) { if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
return scope.resolve(record, localName) return scope.resolve(record, localName)
} }
return when (val value = record.value) { return record.value
is FrameSlotRef -> value.read()
is RecordSlotRef -> value.read(scope, localName)
is ScopeSlotRef -> value.read()
else -> value
}
}
private suspend fun readResolvedScopeRecord(target: Scope, name: String, record: ObjRecord): Obj {
val value = record.value
return when {
record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || value is ObjProperty ->
target.resolve(record, name)
value is FrameSlotRef -> value.read()
value is RecordSlotRef -> value.read(target, name)
value is ScopeSlotRef -> value.read()
else -> value
}
} }
private suspend fun getScopeSlotValue(slot: Int): Obj { private suspend fun getScopeSlotValue(slot: Int): Obj {
@ -5256,7 +5134,10 @@ class CmdFrame(
if (name != null && record.memberName != null && record.memberName != name) { if (name != null && record.memberName != null && record.memberName != name) {
val resolved = target.get(name) val resolved = target.get(name)
if (resolved != null) { if (resolved != null) {
val resolvedValue = readResolvedScopeRecord(target, name, resolved) val resolvedValue = resolved.value
if (resolved.type == ObjRecord.Type.Delegated || resolved.type == ObjRecord.Type.Property || resolvedValue is ObjProperty) {
return target.resolve(resolved, name)
}
if (resolvedValue !== ObjUnset) { if (resolvedValue !== ObjUnset) {
target.updateSlotFor(name, resolved) target.updateSlotFor(name, resolved)
} }
@ -5275,13 +5156,12 @@ class CmdFrame(
failMissingPreparedModuleBinding(slot, name, hadNamedBinding, record) failMissingPreparedModuleBinding(slot, name, hadNamedBinding, record)
return record.value return record.value
} }
val resolvedValue = readResolvedScopeRecord(target, name, resolved) if (resolved.value !== ObjUnset) {
if (resolvedValue !== ObjUnset) {
target.updateSlotFor(name, resolved) target.updateSlotFor(name, resolved)
} else { } else {
failMissingPreparedModuleBinding(slot, name, hadNamedBinding, resolved) failMissingPreparedModuleBinding(slot, name, hadNamedBinding, resolved)
} }
return resolvedValue return resolved.value
} }
private suspend fun getScopeSlotValueAtAddr(addrSlot: Int): Obj { private suspend fun getScopeSlotValueAtAddr(addrSlot: Int): Obj {
@ -5297,7 +5177,10 @@ class CmdFrame(
if (name != null && record.memberName != null && record.memberName != name) { if (name != null && record.memberName != null && record.memberName != name) {
val resolved = target.get(name) val resolved = target.get(name)
if (resolved != null) { if (resolved != null) {
val resolvedValue = readResolvedScopeRecord(target, name, resolved) val resolvedValue = resolved.value
if (resolved.type == ObjRecord.Type.Delegated || resolved.type == ObjRecord.Type.Property || resolvedValue is ObjProperty) {
return target.resolve(resolved, name)
}
if (resolvedValue !== ObjUnset) { if (resolvedValue !== ObjUnset) {
target.updateSlotFor(name, resolved) target.updateSlotFor(name, resolved)
} }
@ -5316,13 +5199,12 @@ class CmdFrame(
failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, record) failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, record)
return record.value return record.value
} }
val resolvedValue = readResolvedScopeRecord(target, name, resolved) if (resolved.value !== ObjUnset) {
if (resolvedValue !== ObjUnset) {
target.updateSlotFor(name, resolved) target.updateSlotFor(name, resolved)
} else { } else {
failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, resolved) failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, resolved)
} }
return resolvedValue return resolved.value
} }
private suspend fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) { private suspend fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {

View File

@ -34,12 +34,7 @@ internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) { val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) {
scope.resolve(record, name) scope.resolve(record, name)
} else { } else {
when (val direct = record.value) { record.value
is net.sergeych.lyng.FrameSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
is net.sergeych.lyng.RecordSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
is net.sergeych.lyng.ScopeSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
else -> direct
}
} }
if (value is net.sergeych.lyng.FrameSlotRef && value.refersTo(frame.frame, i)) { if (value is net.sergeych.lyng.FrameSlotRef && value.refersTo(frame.frame, i)) {
continue continue

View File

@ -16,60 +16,17 @@
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Compiler import net.sergeych.lyng.Compiler
import net.sergeych.lyng.EvalSession
import net.sergeych.lyng.Script import net.sergeych.lyng.Script
import net.sergeych.lyng.Source import net.sergeych.lyng.Source
import net.sergeych.lyng.obj.ObjString
import net.sergeych.lyng.obj.toInt import net.sergeych.lyng.obj.toInt
import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportManager
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertNull import kotlin.test.assertNull
import kotlin.test.assertSame
class ScriptImportPreparationTest { class ScriptImportPreparationTest {
private fun nestedImportSources(prefix: String): Array<String> =
arrayOf(
"""
package $prefix.alpha
class Alpha {
val headers = Map<String, String>()
fun tagged(port: Int, host: String): String {
val task: Deferred = launch {
host + ":" + port + ":" + headers.size
}
return task.await()
}
}
fun alphaValue() = Alpha().tagged(7, "alpha")
""".trimIndent(),
"""
package $prefix.beta
import $prefix.alpha
fun betaValue() = alphaValue() + "|" + Alpha().tagged(8, "beta")
""".trimIndent(),
"""
package $prefix.gamma
import $prefix.alpha
import $prefix.beta
val String.gammaTag get() = this + ":gamma"
fun gammaValue() = betaValue() + "|" + "done".gammaTag
""".trimIndent()
)
private fun nestedImportManager(prefix: String = "tree"): ImportManager =
Script.defaultImportManager.copy().apply {
addTextPackages(*nestedImportSources(prefix))
}
@Test @Test
fun scriptImportIntoExplicitlyPreparesExistingScope() = runTest { fun scriptImportIntoExplicitlyPreparesExistingScope() = runTest {
val manager = ImportManager() val manager = ImportManager()
@ -127,67 +84,4 @@ class ScriptImportPreparationTest {
val record = assertNotNull(module["answer"]) val record = assertNotNull(module["answer"])
assertEquals(42, module.resolve(record, "answer").toInt()) assertEquals(42, module.resolve(record, "answer").toInt())
} }
@Test
fun repeatedImportIntoOnSameScopeIsIdempotentForNestedPackageGraph() = runTest {
val manager = nestedImportManager()
val script = Compiler.compile(
Source(
"<repeat-import-into>",
"""
import tree.gamma
import tree.beta
import tree.alpha
gammaValue()
""".trimIndent()
),
manager
)
val scope = manager.newStdScope()
script.importInto(scope)
val importedGammaValue = assertNotNull(scope["gammaValue"])
val importedAlpha = assertNotNull(scope["Alpha"])
repeat(5) {
script.importInto(scope)
}
assertSame(importedGammaValue, scope["gammaValue"])
assertSame(importedAlpha, scope["Alpha"])
assertEquals(
"alpha:7:0|beta:8:0|done:gamma",
(script.execute(scope) as ObjString).value
)
}
@Test
fun repeatedEvalOnSameSessionCanReimportNestedPackageGraph() = runTest {
val prefix = "repeattree"
val manager = nestedImportManager(prefix)
val scope = manager.newModule()
val session = EvalSession(scope)
try {
repeat(5) { index ->
val result = session.eval(
Source(
"<repeat-eval-$index>",
"""
import $prefix.gamma
import $prefix.beta
import $prefix.alpha
gammaValue()
""".trimIndent()
)
) as ObjString
assertEquals("alpha:7:0|beta:8:0|done:gamma", result.value)
}
} finally {
session.cancelAndJoin()
}
}
} }