Fix implicit extension calls and apply scope captures
This commit is contained in:
1 parent
ecf64dcbc3
commit
68122df6d7
83 files changed
+1502
-146
No files matched your search
@@ -21,6 +21,7 @@ import net.sergeych.lyng.obj.Obj
|
||||
class BlockStatement(
|
||||
val block: Script,
|
||||
val slotPlan: Map<String, Int>,
|
||||
val captureSlots: List<CaptureSlot> = emptyList(),
|
||||
private val startPos: Pos,
|
||||
) : Statement() {
|
||||
override val pos: Pos = startPos
|
||||
@@ -28,8 +29,16 @@ class BlockStatement(
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
val target = if (scope.skipScopeCreation) scope else scope.createChildScope(startPos)
|
||||
if (slotPlan.isNotEmpty()) target.applySlotPlan(slotPlan)
|
||||
if (captureSlots.isNotEmpty()) {
|
||||
for (capture in captureSlots) {
|
||||
val rec = scope.resolveCaptureRecord(capture.name)
|
||||
?: scope.raiseSymbolNotFound("symbol ${capture.name} not found")
|
||||
target.updateSlotFor(capture.name, rec)
|
||||
}
|
||||
}
|
||||
return block.execute(target)
|
||||
}
|
||||
|
||||
fun statements(): List<Statement> = block.debugStatements()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
data class CaptureSlot(
|
||||
val name: String,
|
||||
)
|
||||
@@ -71,7 +71,7 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
||||
}
|
||||
}
|
||||
|
||||
class ApplyScope(_parent: Scope,val applied: Scope) : Scope(_parent, thisObj = applied.thisObj) {
|
||||
class ApplyScope(callScope: Scope, val applied: Scope) : Scope(callScope.parent?.parent ?: callScope.parent ?: callScope, thisObj = applied.thisObj) {
|
||||
|
||||
override fun get(name: String): ObjRecord? {
|
||||
return applied.get(name) ?: super.get(name)
|
||||
@@ -81,4 +81,4 @@ class ApplyScope(_parent: Scope,val applied: Scope) : Scope(_parent, thisObj = a
|
||||
return this
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,9 @@ package net.sergeych.lyng
|
||||
|
||||
sealed class CodeContext {
|
||||
class Module(@Suppress("unused") val packageName: String?): CodeContext()
|
||||
class Function(val name: String): CodeContext()
|
||||
class Function(val name: String, val implicitThisMembers: Boolean = false): CodeContext()
|
||||
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
|
||||
val pendingInitializations = mutableMapOf<String, Pos>()
|
||||
val declaredMembers = mutableSetOf<String>()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,27 +132,29 @@ class Compiler(
|
||||
val nameToken = nextNonWs()
|
||||
if (nameToken.type != Token.Type.ID) continue
|
||||
val afterName = cc.peekNextNonWhitespace()
|
||||
val fnName = if (afterName.type == Token.Type.DOT) {
|
||||
if (afterName.type == Token.Type.DOT) {
|
||||
cc.nextNonWhitespace()
|
||||
val actual = cc.nextNonWhitespace()
|
||||
if (actual.type == Token.Type.ID) actual.value else null
|
||||
} else nameToken.value
|
||||
if (fnName != null) {
|
||||
declareSlotNameIn(plan, fnName, isMutable = false, isDelegated = false)
|
||||
if (actual.type == Token.Type.ID) {
|
||||
extensionNames.add(actual.value)
|
||||
}
|
||||
continue
|
||||
}
|
||||
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
|
||||
}
|
||||
"val", "var" -> {
|
||||
val nameToken = nextNonWs()
|
||||
if (nameToken.type != Token.Type.ID) continue
|
||||
val afterName = cc.peekNextNonWhitespace()
|
||||
val varName = if (afterName.type == Token.Type.DOT) {
|
||||
if (afterName.type == Token.Type.DOT) {
|
||||
cc.nextNonWhitespace()
|
||||
val actual = cc.nextNonWhitespace()
|
||||
if (actual.type == Token.Type.ID) actual.value else null
|
||||
} else nameToken.value
|
||||
if (varName != null) {
|
||||
declareSlotNameIn(plan, varName, isMutable = t.value == "var", isDelegated = false)
|
||||
if (actual.type == Token.Type.ID) {
|
||||
extensionNames.add(actual.value)
|
||||
}
|
||||
continue
|
||||
}
|
||||
declareSlotNameIn(plan, nameToken.value, isMutable = t.value == "var", isDelegated = false)
|
||||
}
|
||||
"class", "object" -> {
|
||||
val nameToken = nextNonWs()
|
||||
@@ -231,6 +233,22 @@ class Compiler(
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
val captureOwner = capturePlanStack.lastOrNull()?.captureOwners?.get(name)
|
||||
if (slotLoc.depth == 0 && captureOwner != null) {
|
||||
val ref = LocalSlotRef(
|
||||
name,
|
||||
slotLoc.slot,
|
||||
slotLoc.scopeId,
|
||||
slotLoc.isMutable,
|
||||
slotLoc.isDelegated,
|
||||
pos,
|
||||
strictSlotRefs,
|
||||
captureOwnerScopeId = captureOwner.scopeId,
|
||||
captureOwnerSlot = captureOwner.slot
|
||||
)
|
||||
resolutionSink?.reference(name, pos)
|
||||
return ref
|
||||
}
|
||||
val ref = LocalSlotRef(
|
||||
name,
|
||||
slotLoc.slot,
|
||||
@@ -301,6 +319,11 @@ class Compiler(
|
||||
resolutionSink?.referenceMember(name, pos)
|
||||
return ImplicitThisMemberRef(name, pos)
|
||||
}
|
||||
val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody }
|
||||
if (classContext && extensionNames.contains(name)) {
|
||||
resolutionSink?.referenceMember(name, pos)
|
||||
return LocalVarRef(name, pos)
|
||||
}
|
||||
resolutionSink?.reference(name, pos)
|
||||
if (allowUnresolvedRefs) {
|
||||
return LocalVarRef(name, pos)
|
||||
@@ -611,6 +634,7 @@ class Compiler(
|
||||
private val allowUnresolvedRefs: Boolean = settings.allowUnresolvedRefs
|
||||
private val returnLabelStack = ArrayDeque<Set<String>>()
|
||||
private val rangeParamNamesStack = mutableListOf<Set<String>>()
|
||||
private val extensionNames = mutableSetOf<String>()
|
||||
private val currentRangeParamNames: Set<String>
|
||||
get() = rangeParamNamesStack.lastOrNull() ?: emptySet()
|
||||
private val capturePlanStack = mutableListOf<CapturePlan>()
|
||||
@@ -618,7 +642,8 @@ class Compiler(
|
||||
private data class CapturePlan(
|
||||
val slotPlan: SlotPlan,
|
||||
val captures: MutableList<CaptureSlot> = mutableListOf(),
|
||||
val captureMap: MutableMap<String, CaptureSlot> = mutableMapOf()
|
||||
val captureMap: MutableMap<String, CaptureSlot> = mutableMapOf(),
|
||||
val captureOwners: MutableMap<String, SlotLocation> = mutableMapOf()
|
||||
)
|
||||
|
||||
private fun recordCaptureSlot(name: String, slotLoc: SlotLocation) {
|
||||
@@ -628,6 +653,7 @@ class Compiler(
|
||||
name = name,
|
||||
)
|
||||
plan.captureMap[name] = capture
|
||||
plan.captureOwners[name] = slotLoc
|
||||
plan.captures += capture
|
||||
if (!plan.slotPlan.slots.containsKey(name)) {
|
||||
plan.slotPlan.slots[name] = SlotEntry(
|
||||
@@ -1383,7 +1409,7 @@ class Compiler(
|
||||
// and the source closure of the lambda which might have other thisObj.
|
||||
val context = scope.applyClosure(closureScope)
|
||||
if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot)
|
||||
if (captureSlots.isNotEmpty()) {
|
||||
if (captureSlots.isNotEmpty() && context !is ApplyScope) {
|
||||
for (capture in captureSlots) {
|
||||
val rec = closureScope.resolveCaptureRecord(capture.name)
|
||||
?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found")
|
||||
@@ -1949,6 +1975,28 @@ class Compiler(
|
||||
return when (left) {
|
||||
is ImplicitThisMemberRef ->
|
||||
ImplicitThisMethodCallRef(left.name, args, detectedBlockArgument, isOptional, left.atPos)
|
||||
is LocalVarRef -> {
|
||||
val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody }
|
||||
val implicitThis = codeContexts.any { ctx ->
|
||||
(ctx as? CodeContext.Function)?.implicitThisMembers == true
|
||||
}
|
||||
if ((classContext || implicitThis) && extensionNames.contains(left.name)) {
|
||||
ImplicitThisMethodCallRef(left.name, args, detectedBlockArgument, isOptional, left.pos())
|
||||
} else {
|
||||
CallRef(left, args, detectedBlockArgument, isOptional)
|
||||
}
|
||||
}
|
||||
is LocalSlotRef -> {
|
||||
val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody }
|
||||
val implicitThis = codeContexts.any { ctx ->
|
||||
(ctx as? CodeContext.Function)?.implicitThisMembers == true
|
||||
}
|
||||
if ((classContext || implicitThis) && extensionNames.contains(left.name)) {
|
||||
ImplicitThisMethodCallRef(left.name, args, detectedBlockArgument, isOptional, left.pos())
|
||||
} else {
|
||||
CallRef(left, args, detectedBlockArgument, isOptional)
|
||||
}
|
||||
}
|
||||
else -> CallRef(left, args, detectedBlockArgument, isOptional)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ class ModuleScope(
|
||||
// when importing records, we keep track of its package (not otherwise needed)
|
||||
if (record.importedFrom == null) record.importedFrom = this
|
||||
scope.objects[newName] = record
|
||||
scope.updateSlotFor(newName, record)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,4 +93,3 @@ class ModuleScope(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,15 @@ import kotlin.math.*
|
||||
class Script(
|
||||
override val pos: Pos,
|
||||
private val statements: List<Statement> = emptyList(),
|
||||
private val moduleSlotPlan: Map<String, Int> = emptyMap(),
|
||||
// private val catchReturn: Boolean = false,
|
||||
) : Statement() {
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
if (moduleSlotPlan.isNotEmpty()) {
|
||||
scope.applySlotPlan(moduleSlotPlan)
|
||||
seedModuleSlots(scope)
|
||||
}
|
||||
var lastResult: Obj = ObjVoid
|
||||
for (s in statements) {
|
||||
lastResult = s.execute(scope)
|
||||
@@ -43,6 +48,17 @@ class Script(
|
||||
return lastResult
|
||||
}
|
||||
|
||||
private fun seedModuleSlots(scope: Scope) {
|
||||
val parent = scope.parent ?: return
|
||||
for (name in moduleSlotPlan.keys) {
|
||||
if (scope.objects.containsKey(name)) {
|
||||
scope.updateSlotFor(name, scope.objects[name]!!)
|
||||
continue
|
||||
}
|
||||
parent.get(name)?.let { scope.updateSlotFor(name, it) }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun debugStatements(): List<Statement> = statements
|
||||
|
||||
suspend fun execute() = execute(
|
||||
|
||||
@@ -27,7 +27,7 @@ class VarDeclStatement(
|
||||
val initializer: Statement?,
|
||||
val isTransient: Boolean,
|
||||
val slotIndex: Int?,
|
||||
val slotDepth: Int?,
|
||||
val scopeId: Int?,
|
||||
private val startPos: Pos,
|
||||
) : Statement() {
|
||||
override val pos: Pos = startPos
|
||||
|
||||
@@ -259,12 +259,43 @@ class BytecodeCompiler(
|
||||
updateSlotType(slot, SlotType.OBJ)
|
||||
CompiledValue(slot, SlotType.OBJ)
|
||||
}
|
||||
is ImplicitThisMethodCallRef -> compileEvalRef(ref)
|
||||
is ImplicitThisMethodCallRef -> compileImplicitThisMethodCall(ref)
|
||||
is IndexRef -> compileIndexRef(ref)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileImplicitThisMethodCall(ref: ImplicitThisMethodCallRef): CompiledValue? {
|
||||
val receiver = compileNameLookup("this")
|
||||
val methodId = builder.addConst(BytecodeConst.StringVal(ref.methodName()))
|
||||
if (methodId > 0xFFFF) return null
|
||||
val dst = allocSlot()
|
||||
if (!ref.optionalInvoke()) {
|
||||
val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
builder.emit(Opcode.CALL_VIRTUAL, receiver.slot, methodId, args.base, encodedCount, dst)
|
||||
return CompiledValue(dst, SlotType.OBJ)
|
||||
}
|
||||
val nullSlot = allocSlot()
|
||||
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||
val cmpSlot = allocSlot()
|
||||
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
|
||||
val nullLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
|
||||
)
|
||||
val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
builder.emit(Opcode.CALL_VIRTUAL, receiver.slot, methodId, args.base, encodedCount, dst)
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||
builder.mark(nullLabel)
|
||||
builder.emit(Opcode.CONST_NULL, dst)
|
||||
builder.mark(endLabel)
|
||||
return CompiledValue(dst, SlotType.OBJ)
|
||||
}
|
||||
|
||||
private fun compileConst(obj: Obj): CompiledValue? {
|
||||
val slot = allocSlot()
|
||||
when (obj) {
|
||||
@@ -1743,6 +1774,42 @@ class BytecodeCompiler(
|
||||
}
|
||||
|
||||
private fun compileCall(ref: CallRef): CompiledValue? {
|
||||
val localTarget = ref.target as? LocalVarRef
|
||||
if (localTarget != null) {
|
||||
val direct = resolveDirectNameSlot(localTarget.name)
|
||||
if (direct == null) {
|
||||
val thisSlot = resolveDirectNameSlot("this")
|
||||
if (thisSlot != null) {
|
||||
val methodId = builder.addConst(BytecodeConst.StringVal(localTarget.name))
|
||||
if (methodId > 0xFFFF) return null
|
||||
val dst = allocSlot()
|
||||
if (!ref.isOptionalInvoke) {
|
||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
builder.emit(Opcode.CALL_VIRTUAL, thisSlot.slot, methodId, args.base, encodedCount, dst)
|
||||
return CompiledValue(dst, SlotType.OBJ)
|
||||
}
|
||||
val nullSlot = allocSlot()
|
||||
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||
val cmpSlot = allocSlot()
|
||||
builder.emit(Opcode.CMP_REF_EQ_OBJ, thisSlot.slot, nullSlot, cmpSlot)
|
||||
val nullLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
|
||||
)
|
||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
builder.emit(Opcode.CALL_VIRTUAL, thisSlot.slot, methodId, args.base, encodedCount, dst)
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||
builder.mark(nullLabel)
|
||||
builder.emit(Opcode.CONST_NULL, dst)
|
||||
builder.mark(endLabel)
|
||||
return CompiledValue(dst, SlotType.OBJ)
|
||||
}
|
||||
}
|
||||
}
|
||||
val fieldTarget = ref.target as? FieldRef
|
||||
if (fieldTarget != null) {
|
||||
val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null
|
||||
@@ -1802,6 +1869,32 @@ class BytecodeCompiler(
|
||||
return CompiledValue(dst, SlotType.OBJ)
|
||||
}
|
||||
|
||||
private fun resolveDirectNameSlot(name: String): CompiledValue? {
|
||||
if (!allowLocalSlots) return null
|
||||
if (pendingScopeNameRefs.contains(name)) return null
|
||||
if (!forceScopeSlots) {
|
||||
scopeSlotIndexByName[name]?.let { slot ->
|
||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||
return CompiledValue(slot, resolved)
|
||||
}
|
||||
loopSlotOverrides[name]?.let { slot ->
|
||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||
return CompiledValue(slot, resolved)
|
||||
}
|
||||
localSlotIndexByName[name]?.let { localIndex ->
|
||||
val slot = scopeSlotCount + localIndex
|
||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||
return CompiledValue(slot, resolved)
|
||||
}
|
||||
return null
|
||||
}
|
||||
scopeSlotIndexByName[name]?.let { slot ->
|
||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||
return CompiledValue(slot, resolved)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun compileMethodCall(ref: MethodCallRef): CompiledValue? {
|
||||
val receiver = compileRefWithFallback(ref.receiver, null, Pos.builtIn) ?: return null
|
||||
val methodId = builder.addConst(BytecodeConst.StringVal(ref.name))
|
||||
@@ -2899,9 +2992,11 @@ class BytecodeCompiler(
|
||||
|
||||
private fun ensureScopeAddr(scopeSlot: Int): Int {
|
||||
val existing = addrSlotByScopeSlot[scopeSlot]
|
||||
if (existing != null) return existing
|
||||
val addrSlot = nextAddrSlot++
|
||||
addrSlotByScopeSlot[scopeSlot] = addrSlot
|
||||
val addrSlot = existing ?: run {
|
||||
val created = nextAddrSlot++
|
||||
addrSlotByScopeSlot[scopeSlot] = created
|
||||
created
|
||||
}
|
||||
builder.emit(Opcode.RESOLVE_SCOPE_SLOT, scopeSlot, addrSlot)
|
||||
return addrSlot
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ sealed class BytecodeConst {
|
||||
data class StatementVal(val statement: net.sergeych.lyng.Statement) : BytecodeConst()
|
||||
data class ListLiteralPlan(val spreads: List<Boolean>) : BytecodeConst()
|
||||
data class ValueFn(val fn: suspend (net.sergeych.lyng.Scope) -> net.sergeych.lyng.obj.ObjRecord) : BytecodeConst()
|
||||
data class SlotPlan(val plan: Map<String, Int>) : BytecodeConst()
|
||||
data class SlotPlan(val plan: Map<String, Int>, val captures: List<String> = emptyList()) : BytecodeConst()
|
||||
data class ExtensionPropertyDecl(
|
||||
val extTypeName: String,
|
||||
val property: ObjProperty,
|
||||
|
||||
@@ -52,7 +52,7 @@ class BytecodeStatement private constructor(
|
||||
if (statement is BytecodeStatement) return statement
|
||||
val hasUnsupported = containsUnsupportedStatement(statement)
|
||||
if (hasUnsupported) {
|
||||
val statementName = statement::class.qualifiedName ?: statement.javaClass.name
|
||||
val statementName = statement::class.qualifiedName ?: statement::class.simpleName ?: "UnknownStatement"
|
||||
throw BytecodeFallbackException(
|
||||
"Bytecode fallback: unsupported statement $statementName in '$nameHint'",
|
||||
statement.pos
|
||||
@@ -135,6 +135,7 @@ class BytecodeStatement private constructor(
|
||||
net.sergeych.lyng.BlockStatement(
|
||||
net.sergeych.lyng.Script(stmt.pos, unwrapped),
|
||||
stmt.slotPlan,
|
||||
stmt.captureSlots,
|
||||
stmt.pos
|
||||
)
|
||||
}
|
||||
@@ -146,7 +147,7 @@ class BytecodeStatement private constructor(
|
||||
stmt.initializer?.let { unwrapDeep(it) },
|
||||
stmt.isTransient,
|
||||
stmt.slotIndex,
|
||||
stmt.slotDepth,
|
||||
stmt.scopeId,
|
||||
stmt.pos
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1549,6 +1549,9 @@ class CmdFrame(
|
||||
|
||||
fun pushScope(plan: Map<String, Int>, captures: List<String>) {
|
||||
val parentScope = scope
|
||||
if (captures.isNotEmpty()) {
|
||||
syncFrameToScope()
|
||||
}
|
||||
val captureRecords = if (captures.isNotEmpty()) {
|
||||
captures.map { name ->
|
||||
val rec = parentScope.resolveCaptureRecord(name)
|
||||
@@ -1558,9 +1561,6 @@ class CmdFrame(
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
if (shouldSyncLocalCaptures(captures)) {
|
||||
syncFrameToScope()
|
||||
}
|
||||
if (scope.skipScopeCreation) {
|
||||
val snapshot = scope.applySlotPlanWithSnapshot(plan)
|
||||
slotPlanStack.addLast(snapshot)
|
||||
@@ -1597,7 +1597,7 @@ class CmdFrame(
|
||||
?: error("Scope stack underflow in POP_SCOPE")
|
||||
val captures = captureStack.removeLastOrNull() ?: emptyList()
|
||||
scopeDepth -= 1
|
||||
if (shouldSyncLocalCaptures(captures)) {
|
||||
if (captures.isNotEmpty()) {
|
||||
syncFrameToScope()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1917,6 +1917,7 @@ class ThisMethodSlotCallRef(
|
||||
* Reference to a local/visible variable by name (Phase A: scope lookup).
|
||||
*/
|
||||
class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
internal fun pos(): Pos = atPos
|
||||
override fun forEachVariable(block: (String) -> Unit) {
|
||||
block(name)
|
||||
}
|
||||
@@ -2287,14 +2288,7 @@ class ImplicitThisMemberRef(
|
||||
val caller = scope.currentClassCtx
|
||||
val th = scope.thisObj
|
||||
|
||||
// 1) locals in the same `this` chain
|
||||
var s: Scope? = scope
|
||||
while (s != null && s.thisObj === th) {
|
||||
scope.tryGetLocalRecord(s, name, caller)?.let { return it }
|
||||
s = s.parent
|
||||
}
|
||||
|
||||
// 2) member slots on this instance
|
||||
// member slots on this instance
|
||||
if (th is ObjInstance) {
|
||||
// private member access for current class context
|
||||
caller?.let { c ->
|
||||
@@ -2326,14 +2320,7 @@ class ImplicitThisMemberRef(
|
||||
}
|
||||
}
|
||||
|
||||
// 3) fallback to normal scope resolution (globals/outer scopes)
|
||||
scope[name]?.let { return it }
|
||||
try {
|
||||
return th.readField(scope, name)
|
||||
} catch (e: ExecutionError) {
|
||||
if ((e.message ?: "").contains("no such field: $name")) scope.raiseSymbolNotFound(name)
|
||||
throw e
|
||||
}
|
||||
scope.raiseSymbolNotFound(name)
|
||||
}
|
||||
|
||||
override suspend fun evalValue(scope: Scope): Obj {
|
||||
@@ -2346,18 +2333,7 @@ class ImplicitThisMemberRef(
|
||||
val caller = scope.currentClassCtx
|
||||
val th = scope.thisObj
|
||||
|
||||
// 1) locals in the same `this` chain
|
||||
var s: Scope? = scope
|
||||
while (s != null && s.thisObj === th) {
|
||||
val rec = scope.tryGetLocalRecord(s, name, caller)
|
||||
if (rec != null) {
|
||||
scope.assign(rec, name, newValue)
|
||||
return
|
||||
}
|
||||
s = s.parent
|
||||
}
|
||||
|
||||
// 2) member slots on this instance
|
||||
// member slots on this instance
|
||||
if (th is ObjInstance) {
|
||||
val key = th.objClass.publicMemberResolution[name] ?: name
|
||||
th.fieldRecordForKey(key)?.let { rec ->
|
||||
@@ -2388,12 +2364,7 @@ class ImplicitThisMemberRef(
|
||||
}
|
||||
}
|
||||
|
||||
// 3) fallback to normal scope resolution
|
||||
scope[name]?.let { stored ->
|
||||
scope.assign(stored, name, newValue)
|
||||
return
|
||||
}
|
||||
th.writeField(scope, name, newValue)
|
||||
scope.raiseSymbolNotFound(name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2409,22 +2380,30 @@ class ImplicitThisMethodCallRef(
|
||||
private val atPos: Pos
|
||||
) : ObjRef {
|
||||
private val memberRef = ImplicitThisMemberRef(name, atPos)
|
||||
internal fun methodName(): String = name
|
||||
internal fun arguments(): List<ParsedArgument> = args
|
||||
internal fun hasTailBlock(): Boolean = tailBlock
|
||||
internal fun optionalInvoke(): Boolean = isOptional
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
|
||||
|
||||
override suspend fun evalValue(scope: Scope): Obj {
|
||||
scope.pos = atPos
|
||||
val callee = memberRef.evalValue(scope)
|
||||
if (callee == ObjNull && isOptional) return ObjNull
|
||||
val callArgs = args.toArguments(scope, tailBlock)
|
||||
val usePool = PerfFlags.SCOPE_POOL
|
||||
return if (usePool) {
|
||||
scope.withChildFrame(callArgs) { child ->
|
||||
callee.callOn(child)
|
||||
val localRecord = scope.chainLookupIgnoreClosure(name, followClosure = true, caller = scope.currentClassCtx)
|
||||
if (localRecord != null) {
|
||||
val callee = scope.resolve(localRecord, name)
|
||||
if (callee == ObjNull && isOptional) return ObjNull
|
||||
val usePool = PerfFlags.SCOPE_POOL
|
||||
return if (usePool) {
|
||||
scope.withChildFrame(callArgs) { child ->
|
||||
callee.callOn(child)
|
||||
}
|
||||
} else {
|
||||
callee.callOn(scope.createChildScope(scope.pos, callArgs))
|
||||
}
|
||||
} else {
|
||||
callee.callOn(scope.createChildScope(scope.pos, callArgs))
|
||||
}
|
||||
return scope.thisObj.invokeInstanceMethod(scope, name, callArgs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2435,57 +2414,31 @@ class ImplicitThisMethodCallRef(
|
||||
class LocalSlotRef(
|
||||
val name: String,
|
||||
internal val slot: Int,
|
||||
internal val depth: Int,
|
||||
internal val scopeDepth: Int,
|
||||
internal val scopeId: Int,
|
||||
internal val isMutable: Boolean,
|
||||
internal val isDelegated: Boolean,
|
||||
private val atPos: Pos,
|
||||
private val strict: Boolean = false,
|
||||
internal val captureOwnerScopeId: Int? = null,
|
||||
internal val captureOwnerSlot: Int? = null,
|
||||
) : ObjRef {
|
||||
internal fun pos(): Pos = atPos
|
||||
override fun forEachVariable(block: (String) -> Unit) {
|
||||
block(name)
|
||||
}
|
||||
|
||||
private val fallbackRef = LocalVarRef(name, atPos)
|
||||
private var cachedFrameId: Long = 0L
|
||||
private var cachedOwner: Scope? = null
|
||||
private var cachedOwnerVerified: Boolean = false
|
||||
|
||||
private fun resolveOwner(scope: Scope): Scope? {
|
||||
if (cachedOwner != null && cachedFrameId == scope.frameId && cachedOwnerVerified) {
|
||||
val cached = cachedOwner!!
|
||||
val candidate = if (depth == 0) scope else {
|
||||
var s: Scope? = scope
|
||||
var remaining = depth
|
||||
while (s != null && remaining > 0) {
|
||||
s = s.parent
|
||||
remaining--
|
||||
}
|
||||
s
|
||||
}
|
||||
if (candidate === cached && candidate?.getSlotIndexOf(name) == slot) return cached
|
||||
}
|
||||
var s: Scope? = scope
|
||||
var remaining = depth
|
||||
while (s != null && remaining > 0) {
|
||||
s = s.parent
|
||||
remaining--
|
||||
}
|
||||
if (s == null || s.getSlotIndexOf(name) != slot) {
|
||||
cachedOwner = null
|
||||
cachedOwnerVerified = false
|
||||
cachedFrameId = scope.frameId
|
||||
return null
|
||||
}
|
||||
cachedOwner = s
|
||||
cachedOwnerVerified = true
|
||||
cachedFrameId = scope.frameId
|
||||
return s
|
||||
private fun resolveOwner(scope: Scope): Scope {
|
||||
return scope
|
||||
}
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
scope.pos = atPos
|
||||
val owner = resolveOwner(scope) ?: return fallbackRef.get(scope)
|
||||
if (slot < 0 || slot >= owner.slotCount()) return fallbackRef.get(scope)
|
||||
val owner = resolveOwner(scope)
|
||||
if (slot < 0 || slot >= owner.slotCount()) {
|
||||
if (strict) scope.raiseError("slot index out of range for $name")
|
||||
return fallbackRef.get(scope)
|
||||
}
|
||||
val rec = owner.getSlotRecord(slot)
|
||||
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
|
||||
@@ -2495,8 +2448,11 @@ class LocalSlotRef(
|
||||
|
||||
override suspend fun evalValue(scope: Scope): Obj {
|
||||
scope.pos = atPos
|
||||
val owner = resolveOwner(scope) ?: return fallbackRef.evalValue(scope)
|
||||
if (slot < 0 || slot >= owner.slotCount()) return fallbackRef.evalValue(scope)
|
||||
val owner = resolveOwner(scope)
|
||||
if (slot < 0 || slot >= owner.slotCount()) {
|
||||
if (strict) scope.raiseError("slot index out of range for $name")
|
||||
return fallbackRef.evalValue(scope)
|
||||
}
|
||||
val rec = owner.getSlotRecord(slot)
|
||||
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
|
||||
@@ -2506,11 +2462,9 @@ class LocalSlotRef(
|
||||
|
||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
scope.pos = atPos
|
||||
val owner = resolveOwner(scope) ?: run {
|
||||
fallbackRef.setAt(pos, scope, newValue)
|
||||
return
|
||||
}
|
||||
val owner = resolveOwner(scope)
|
||||
if (slot < 0 || slot >= owner.slotCount()) {
|
||||
if (strict) scope.raiseError("slot index out of range for $name")
|
||||
fallbackRef.setAt(pos, scope, newValue)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.resolution
|
||||
|
||||
import net.sergeych.lyng.Pos
|
||||
|
||||
class ResolutionCollector(private val moduleName: String) : ResolutionSink {
|
||||
|
||||
private data class Decl(
|
||||
val name: String,
|
||||
val kind: SymbolKind,
|
||||
val isMutable: Boolean,
|
||||
val pos: Pos,
|
||||
val isOverride: Boolean
|
||||
)
|
||||
|
||||
private data class Ref(
|
||||
val name: String,
|
||||
val pos: Pos,
|
||||
val qualifier: String? = null
|
||||
)
|
||||
|
||||
private data class ReflectRef(
|
||||
val name: String,
|
||||
val pos: Pos
|
||||
)
|
||||
|
||||
private data class MemberInfo(
|
||||
val name: String,
|
||||
val isOverride: Boolean,
|
||||
val pos: Pos
|
||||
)
|
||||
|
||||
private data class ClassInfo(
|
||||
val name: String,
|
||||
val bases: List<String>,
|
||||
val pos: Pos,
|
||||
val members: MutableMap<String, MemberInfo> = LinkedHashMap()
|
||||
)
|
||||
|
||||
private class ScopeNode(
|
||||
val kind: ScopeKind,
|
||||
val pos: Pos,
|
||||
val parent: ScopeNode?,
|
||||
val className: String? = null,
|
||||
val bases: List<String> = emptyList()
|
||||
) {
|
||||
val decls: LinkedHashMap<String, Decl> = LinkedHashMap()
|
||||
val refs: MutableList<Ref> = ArrayList()
|
||||
val memberRefs: MutableList<Ref> = ArrayList()
|
||||
val reflectRefs: MutableList<ReflectRef> = ArrayList()
|
||||
val captures: LinkedHashMap<String, CaptureInfo> = LinkedHashMap()
|
||||
val children: MutableList<ScopeNode> = ArrayList()
|
||||
}
|
||||
|
||||
private var root: ScopeNode? = null
|
||||
private var current: ScopeNode? = null
|
||||
|
||||
private val symbols = ArrayList<ResolvedSymbol>()
|
||||
private val captures = LinkedHashMap<String, CaptureInfo>()
|
||||
private val errors = ArrayList<ResolutionError>()
|
||||
private val warnings = ArrayList<ResolutionWarning>()
|
||||
private val classes = LinkedHashMap<String, ClassInfo>()
|
||||
|
||||
override fun enterScope(kind: ScopeKind, pos: Pos, className: String?, bases: List<String>) {
|
||||
val parent = current
|
||||
val node = ScopeNode(kind, pos, parent, className, bases)
|
||||
if (root == null) {
|
||||
root = node
|
||||
}
|
||||
parent?.children?.add(node)
|
||||
current = node
|
||||
if (kind == ScopeKind.CLASS && className != null) {
|
||||
classes.getOrPut(className) { ClassInfo(className, bases.toList(), pos) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun exitScope(pos: Pos) {
|
||||
current = current?.parent
|
||||
}
|
||||
|
||||
override fun declareClass(name: String, bases: List<String>, pos: Pos) {
|
||||
val existing = classes[name]
|
||||
if (existing == null) {
|
||||
classes[name] = ClassInfo(name, bases.toList(), pos)
|
||||
} else if (existing.bases.isEmpty() && bases.isNotEmpty()) {
|
||||
classes[name] = existing.copy(bases = bases.toList())
|
||||
}
|
||||
}
|
||||
|
||||
override fun declareSymbol(
|
||||
name: String,
|
||||
kind: SymbolKind,
|
||||
isMutable: Boolean,
|
||||
pos: Pos,
|
||||
isOverride: Boolean
|
||||
) {
|
||||
val node = current ?: return
|
||||
node.decls[name] = Decl(name, kind, isMutable, pos, isOverride)
|
||||
if (kind == SymbolKind.LOCAL || kind == SymbolKind.PARAM) {
|
||||
val classScope = findNearestClassScope(node)
|
||||
if (classScope != null && classScope.decls.containsKey(name)) {
|
||||
warnings += ResolutionWarning("shadowing member: $name", pos)
|
||||
}
|
||||
}
|
||||
if (kind == SymbolKind.MEMBER) {
|
||||
val classScope = findNearestClassScope(node)
|
||||
val className = classScope?.className
|
||||
if (className != null) {
|
||||
val info = classes.getOrPut(className) { ClassInfo(className, classScope.bases, classScope.pos) }
|
||||
info.members[name] = MemberInfo(name, isOverride, pos)
|
||||
}
|
||||
}
|
||||
symbols += ResolvedSymbol(
|
||||
name = name,
|
||||
origin = originForDecl(node, kind),
|
||||
slotIndex = -1,
|
||||
pos = pos
|
||||
)
|
||||
}
|
||||
|
||||
override fun reference(name: String, pos: Pos) {
|
||||
val node = current ?: return
|
||||
node.refs += Ref(name, pos)
|
||||
}
|
||||
|
||||
override fun referenceMember(name: String, pos: Pos, qualifier: String?) {
|
||||
val node = current ?: return
|
||||
node.memberRefs += Ref(name, pos, qualifier)
|
||||
}
|
||||
|
||||
override fun referenceReflection(name: String, pos: Pos) {
|
||||
val node = current ?: return
|
||||
node.reflectRefs += ReflectRef(name, pos)
|
||||
}
|
||||
|
||||
fun buildReport(): ResolutionReport {
|
||||
root?.let { resolveScope(it) }
|
||||
checkMiConflicts()
|
||||
return ResolutionReport(
|
||||
moduleName = moduleName,
|
||||
symbols = symbols.toList(),
|
||||
captures = captures.values.toList(),
|
||||
errors = errors.toList(),
|
||||
warnings = warnings.toList()
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveScope(node: ScopeNode) {
|
||||
for (ref in node.refs) {
|
||||
if (ref.name == "this") continue
|
||||
if (ref.name == "scope") continue
|
||||
val resolved = resolveName(node, ref)
|
||||
if (!resolved) {
|
||||
errors += ResolutionError("unresolved name: ${ref.name}", ref.pos)
|
||||
}
|
||||
}
|
||||
for (ref in node.memberRefs) {
|
||||
val resolved = resolveMemberName(node, ref)
|
||||
if (!resolved) {
|
||||
errors += ResolutionError("unresolved member: ${ref.name}", ref.pos)
|
||||
}
|
||||
}
|
||||
for (ref in node.reflectRefs) {
|
||||
val resolved = resolveName(node, Ref(ref.name, ref.pos)) ||
|
||||
resolveMemberName(node, Ref(ref.name, ref.pos))
|
||||
if (!resolved) {
|
||||
errors += ResolutionError("unresolved reflected name: ${ref.name}", ref.pos)
|
||||
}
|
||||
}
|
||||
for (child in node.children) {
|
||||
resolveScope(child)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveName(node: ScopeNode, ref: Ref): Boolean {
|
||||
if (ref.name.contains('.')) return true
|
||||
var scope: ScopeNode? = node
|
||||
while (scope != null) {
|
||||
val decl = scope.decls[ref.name]
|
||||
if (decl != null) {
|
||||
if (scope !== node) {
|
||||
recordCapture(node, decl, scope)
|
||||
}
|
||||
return true
|
||||
}
|
||||
scope = scope.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun recordCapture(owner: ScopeNode, decl: Decl, targetScope: ScopeNode) {
|
||||
if (owner.captures.containsKey(decl.name)) return
|
||||
val origin = when (targetScope.kind) {
|
||||
ScopeKind.MODULE -> SymbolOrigin.MODULE
|
||||
else -> SymbolOrigin.OUTER
|
||||
}
|
||||
val capture = CaptureInfo(
|
||||
name = decl.name,
|
||||
origin = origin,
|
||||
slotIndex = -1,
|
||||
isMutable = decl.isMutable,
|
||||
pos = decl.pos
|
||||
)
|
||||
owner.captures[decl.name] = capture
|
||||
captures[decl.name] = capture
|
||||
}
|
||||
|
||||
private fun resolveMemberName(node: ScopeNode, ref: Ref): Boolean {
|
||||
val classScope = findNearestClassScope(node) ?: return false
|
||||
val className = classScope.className ?: return false
|
||||
val qualifier = ref.qualifier
|
||||
return if (qualifier != null) {
|
||||
resolveQualifiedMember(className, qualifier, ref.name, ref.pos)
|
||||
} else {
|
||||
resolveMemberInClass(className, ref.name, ref.pos)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findNearestClassScope(node: ScopeNode): ScopeNode? {
|
||||
var scope: ScopeNode? = node
|
||||
while (scope != null) {
|
||||
if (scope.kind == ScopeKind.CLASS) return scope
|
||||
scope = scope.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun originForDecl(scope: ScopeNode, kind: SymbolKind): SymbolOrigin {
|
||||
return when (kind) {
|
||||
SymbolKind.PARAM -> SymbolOrigin.PARAM
|
||||
SymbolKind.MEMBER -> SymbolOrigin.MEMBER
|
||||
else -> when (scope.kind) {
|
||||
ScopeKind.MODULE -> SymbolOrigin.MODULE
|
||||
ScopeKind.CLASS -> SymbolOrigin.MEMBER
|
||||
else -> SymbolOrigin.LOCAL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveMemberInClass(className: String, member: String, pos: Pos): Boolean {
|
||||
val info = classes[className] ?: return false
|
||||
val currentMember = info.members[member]
|
||||
val definers = findDefiningClasses(className, member)
|
||||
if (currentMember != null) {
|
||||
if (definers.size > 1 && !currentMember.isOverride) {
|
||||
errors += ResolutionError("override required for $member in $className", pos)
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (definers.size > 1) {
|
||||
errors += ResolutionError("ambiguous member '$member' in $className", pos)
|
||||
return true
|
||||
}
|
||||
return definers.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun resolveQualifiedMember(className: String, qualifier: String, member: String, pos: Pos): Boolean {
|
||||
val mro = linearize(className)
|
||||
val idx = mro.indexOf(qualifier)
|
||||
if (idx < 0) return false
|
||||
for (name in mro.drop(idx)) {
|
||||
val info = classes[name]
|
||||
if (info?.members?.containsKey(member) == true) return true
|
||||
}
|
||||
errors += ResolutionError("member '$member' not found in $qualifier", pos)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun findDefiningClasses(className: String, member: String): List<String> {
|
||||
val parents = linearize(className).drop(1)
|
||||
val raw = parents.filter { classes[it]?.members?.containsKey(member) == true }
|
||||
if (raw.size <= 1) return raw
|
||||
val filtered = raw.toMutableList()
|
||||
val iterator = raw.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val candidate = iterator.next()
|
||||
for (other in raw) {
|
||||
if (candidate == other) continue
|
||||
if (linearize(other).contains(candidate)) {
|
||||
filtered.remove(candidate)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
private fun linearize(className: String, visited: MutableMap<String, List<String>> = mutableMapOf()): List<String> {
|
||||
visited[className]?.let { return it }
|
||||
val info = classes[className]
|
||||
val parents = info?.bases ?: emptyList()
|
||||
if (parents.isEmpty()) {
|
||||
val single = listOf(className)
|
||||
visited[className] = single
|
||||
return single
|
||||
}
|
||||
val parentLinearizations = parents.map { linearize(it, visited).toMutableList() }
|
||||
val merge = mutableListOf<MutableList<String>>()
|
||||
merge.addAll(parentLinearizations)
|
||||
merge.add(parents.toMutableList())
|
||||
val merged = c3Merge(merge)
|
||||
val result = listOf(className) + merged
|
||||
visited[className] = result
|
||||
return result
|
||||
}
|
||||
|
||||
private fun c3Merge(seqs: MutableList<MutableList<String>>): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
while (seqs.isNotEmpty()) {
|
||||
seqs.removeAll { it.isEmpty() }
|
||||
if (seqs.isEmpty()) break
|
||||
var candidate: String? = null
|
||||
outer@ for (seq in seqs) {
|
||||
val head = seq.first()
|
||||
var inTail = false
|
||||
for (other in seqs) {
|
||||
if (other === seq || other.size <= 1) continue
|
||||
if (other.drop(1).contains(head)) {
|
||||
inTail = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!inTail) {
|
||||
candidate = head
|
||||
break@outer
|
||||
}
|
||||
}
|
||||
val picked = candidate ?: run {
|
||||
errors += ResolutionError("C3 MRO failed for $moduleName", Pos.builtIn)
|
||||
return result
|
||||
}
|
||||
result += picked
|
||||
for (seq in seqs) {
|
||||
if (seq.isNotEmpty() && seq.first() == picked) {
|
||||
seq.removeAt(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun checkMiConflicts() {
|
||||
for (info in classes.values) {
|
||||
val baseNames = linearize(info.name).drop(1)
|
||||
if (baseNames.isEmpty()) continue
|
||||
val baseMemberNames = linkedSetOf<String>()
|
||||
for (base in baseNames) {
|
||||
classes[base]?.members?.keys?.let { baseMemberNames.addAll(it) }
|
||||
}
|
||||
for (member in baseMemberNames) {
|
||||
val definers = findDefiningClasses(info.name, member)
|
||||
if (definers.size <= 1) continue
|
||||
val current = info.members[member]
|
||||
if (current == null || !current.isOverride) {
|
||||
errors += ResolutionError("ambiguous member '$member' in ${info.name}", info.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.resolution
|
||||
|
||||
import net.sergeych.lyng.Pos
|
||||
|
||||
enum class ScopeKind {
|
||||
MODULE,
|
||||
FUNCTION,
|
||||
BLOCK,
|
||||
CLASS
|
||||
}
|
||||
|
||||
enum class SymbolKind {
|
||||
LOCAL,
|
||||
PARAM,
|
||||
FUNCTION,
|
||||
CLASS,
|
||||
ENUM,
|
||||
MEMBER
|
||||
}
|
||||
|
||||
interface ResolutionSink {
|
||||
fun enterScope(kind: ScopeKind, pos: Pos, className: String? = null, bases: List<String> = emptyList()) {}
|
||||
fun exitScope(pos: Pos) {}
|
||||
fun declareClass(name: String, bases: List<String>, pos: Pos) {}
|
||||
fun declareSymbol(
|
||||
name: String,
|
||||
kind: SymbolKind,
|
||||
isMutable: Boolean,
|
||||
pos: Pos,
|
||||
isOverride: Boolean = false
|
||||
) {}
|
||||
fun reference(name: String, pos: Pos) {}
|
||||
fun referenceMember(name: String, pos: Pos, qualifier: String? = null) {}
|
||||
fun referenceReflection(name: String, pos: Pos) {}
|
||||
}
|
||||
Reference in new issue
Block a user