From 8b196c7b0c604867742b5e95dae685238c845c77 Mon Sep 17 00:00:00 2001 From: sergeych Date: Tue, 10 Feb 2026 07:58:29 +0300 Subject: [PATCH] Step 24E: isolate interpreter-only capture logic --- bytecode_migration_plan.md | 2 +- .../src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt | 1 + lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt | 1 + lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt | 3 +++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/bytecode_migration_plan.md b/bytecode_migration_plan.md index 03ca512..a94fa85 100644 --- a/bytecode_migration_plan.md +++ b/bytecode_migration_plan.md @@ -105,7 +105,7 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te - [ ] Avoid `ClosureScope` in bytecode-related call paths (Block/Lambda/ObjDynamic/ObjProperty). - [ ] Keep interpreter path using `ClosureScope` until interpreter removal. - [ ] JVM tests must be green before commit. -- [ ] Step 24E: Isolate interpreter-only capture logic. +- [x] Step 24E: Isolate interpreter-only capture logic. - [ ] Mark `resolveCaptureRecord` paths as interpreter-only. - [ ] Guard or delete any bytecode path that tries to sync captures into scopes. - [ ] JVM tests must be green before commit. diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt index 5860cd2..43194f7 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt @@ -33,6 +33,7 @@ class BlockStatement( if (captureSlots.isNotEmpty()) { val applyScope = scope as? ApplyScope for (capture in captureSlots) { + // Interpreter-only capture resolution; bytecode paths must use captureRecords instead. val rec = if (applyScope != null) { applyScope.resolveCaptureRecord(capture.name) ?: applyScope.callScope.resolveCaptureRecord(capture.name) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 9b3bde7..36eb73e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -7034,6 +7034,7 @@ class Compiler( val captureBase = captureContext ?: closure if (captureBase != null && captureSlots.isNotEmpty()) { for (capture in captureSlots) { + // Interpreter-only capture resolution; bytecode functions do not use resolveCaptureRecord. val rec = captureBase.resolveCaptureRecord(capture.name) ?: captureBase.raiseSymbolNotFound("symbol ${capture.name} not found") context.updateSlotFor(capture.name, rec) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index e90f156..265ca37 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -170,6 +170,9 @@ open class Scope( } internal fun resolveCaptureRecord(name: String): ObjRecord? { + if (captureRecords != null) { + raiseIllegalState("resolveCaptureRecord is interpreter-only; bytecode captures use captureRecords") + } return chainLookupIgnoreClosure(name, followClosure = true, caller = currentClassCtx) }