Add nested declarations and lifted enums

This commit is contained in:
Sergey Chernov 2026-02-05 23:57:34 +03:00
parent c35efdc2ae
commit 3b290116b8
11 changed files with 541 additions and 110 deletions

View File

@ -25,6 +25,16 @@ Point(x:, y:).dist() //< 5
fun swapEnds(first, args..., last, f) { fun swapEnds(first, args..., last, f) {
f( last, ...args, first) f( last, ...args, first)
} }
class A {
class B(x?)
object Inner { val foo = "bar" }
enum E* { One, Two }
}
val ab = A.B()
assertEquals(ab.x, null)
assertEquals(A.Inner.foo, "bar")
assertEquals(A.One, A.E.One)
``` ```
- extremely simple Kotlin integration on any platform (JVM, JS, WasmJS, Lunux, MacOS, iOS, Windows) - extremely simple Kotlin integration on any platform (JVM, JS, WasmJS, Lunux, MacOS, iOS, Windows)

View File

@ -113,6 +113,48 @@ val handler = object {
- **Serialization**: Anonymous objects are **not serializable**. Attempting to encode an anonymous object via `Lynon` will throw a `SerializationException`. This is because their class definition is transient and cannot be safely restored in a different session or process. - **Serialization**: Anonymous objects are **not serializable**. Attempting to encode an anonymous object via `Lynon` will throw a `SerializationException`. This is because their class definition is transient and cannot be safely restored in a different session or process.
- **Type Identity**: Every object expression creates a unique anonymous class. Two identical object expressions will result in two different classes with distinct type identities. - **Type Identity**: Every object expression creates a unique anonymous class. Two identical object expressions will result in two different classes with distinct type identities.
## Nested Declarations
Lyng allows classes, objects, enums, and type aliases to be declared inside another class. These declarations live in the **class namespace** (not the instance), so they do not capture an outer instance and are accessed with a qualifier.
```lyng
class A {
class B(x?)
object Inner { val foo = "bar" }
type Alias = B
enum E { One, Two }
}
val ab = A.B()
assertEquals(ab.x, null)
assertEquals(A.Inner.foo, "bar")
```
Rules:
- **Qualified access**: use `Outer.Inner` for nested classes/objects/enums/aliases. Inside `Outer` you can refer to them by unqualified name unless shadowed.
- **No inner semantics**: nested declarations do not capture an instance of the outer class. They are resolved at compile time.
- **Visibility**: `private` restricts a nested declaration to the declaring class body (not visible from outside or subclasses).
- **Reflection name**: a nested class reports `Outer.Inner` (e.g., `A.B::class.name` is `"A.B"`).
- **Type aliases**: behave as aliases of the qualified nested type and are expanded by the type system.
### Lifted Enum Entries
Enums can optionally lift their entries into the surrounding class namespace using `*`:
```lyng
class A {
enum E* { One, Two }
}
assertEquals(A.One, A.E.One)
assertEquals(A.Two, A.E.Two)
```
Notes:
- `E*` exposes entries in `A` as if they were direct members (`A.One`).
- If a name would conflict with an existing class member, compilation fails (no implicit fallback).
- Without `*`, use the normal `A.E.One` form.
## Properties ## Properties
Properties allow you to define member accessors that look like fields but execute code when read or written. Unlike regular fields, properties in Lyng do **not** have automatic backing fields; they are pure accessors. Properties allow you to define member accessors that look like fields but execute code when read or written. Unlike regular fields, properties in Lyng do **not** have automatic backing fields; they are pure accessors.

View File

@ -107,6 +107,23 @@ Singleton objects are declared using the `object` keyword. They define a class a
Logger.log("Hello singleton!") Logger.log("Hello singleton!")
## Nested Declarations (short)
Classes, objects, and enums can be declared inside another class. They live in the class namespace (no outer instance capture), so you access them with a qualifier:
class A {
class B(x?)
object Inner { val foo = "bar" }
enum E* { One, Two }
}
val ab = A.B()
assertEquals(ab.x, null)
assertEquals(A.Inner.foo, "bar")
assertEquals(A.One, A.E.One)
See [OOP notes](OOP.md#nested-declarations) for rules, visibility, and enum lifting details.
## Delegation (briefly) ## Delegation (briefly)
You can delegate properties and functions to other objects using the `by` keyword. This is perfect for patterns like `lazy` initialization. You can delegate properties and functions to other objects using the `by` keyword. This is perfect for patterns like `lazy` initialization.

View File

@ -108,6 +108,24 @@ object Config {
Config.show() Config.show()
``` ```
### Nested Declarations and Lifted Enums
You can now declare classes, objects, enums, and type aliases inside another class. These nested declarations live in the class namespace (no outer instance capture) and are accessed with a qualifier.
```lyng
class A {
class B(x?)
object Inner { val foo = "bar" }
enum E* { One, Two }
}
val ab = A.B()
assertEquals(ab.x, null)
assertEquals(A.Inner.foo, "bar")
assertEquals(A.One, A.E.One)
```
The `*` on `enum E*` lifts entries into the enclosing class namespace (compile-time error on ambiguity).
### Object Expressions ### Object Expressions
You can now create anonymous objects that inherit from classes or interfaces using the `object : Base { ... }` syntax. These expressions capture their lexical scope and support multiple inheritance. You can now create anonymous objects that inherit from classes or interfaces using the `object : Base { ... }` syntax. These expressions capture their lexical scope and support multiple inheritance.

View File

@ -20,6 +20,7 @@ package net.sergeych.lyng
import net.sergeych.lyng.miniast.MiniTypeRef import net.sergeych.lyng.miniast.MiniTypeRef
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjList import net.sergeych.lyng.obj.ObjList
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjRecord import net.sergeych.lyng.obj.ObjRecord
/** /**
@ -61,12 +62,20 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
} }
} }
if (!hasComplex) { if (!hasComplex) {
if (arguments.list.size != params.size) if (arguments.list.size > params.size)
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}") scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
if (arguments.list.size < params.size) {
for (i in arguments.list.size until params.size) {
val a = params[i]
if (!a.type.isNullable) {
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
}
}
}
for (i in params.indices) { for (i in params.indices) {
val a = params[i] val a = params[i]
val value = arguments.list[i] val value = if (i < arguments.list.size) arguments.list[i] else ObjNull
val recordType = if (declaringClass != null && a.accessType != null) { val recordType = if (declaringClass != null && a.accessType != null) {
ObjRecord.Type.ConstructorField ObjRecord.Type.ConstructorField
} else { } else {
@ -103,6 +112,11 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
) )
} }
suspend fun missingValue(a: Item, error: String): Obj {
return a.defaultValue?.execute(scope)
?: if (a.type.isNullable) ObjNull else scope.raiseIllegalArgument(error)
}
// Prepare positional args and parameter count, handle tail-block binding // Prepare positional args and parameter count, handle tail-block binding
val callArgs: List<Obj> val callArgs: List<Obj>
val paramsSize: Int val paramsSize: Int
@ -181,8 +195,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
assign(a, namedValues[i]!!) assign(a, namedValues[i]!!)
} else { } else {
val value = if (hp < callArgs.size) callArgs[hp++] val value = if (hp < callArgs.size) callArgs[hp++]
else a.defaultValue?.execute(scope) else missingValue(a, "too few arguments for the call (missing ${a.name})")
?: scope.raiseIllegalArgument("too few arguments for the call (missing ${a.name})")
assign(a, value) assign(a, value)
} }
i++ i++
@ -202,8 +215,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
assign(a, namedValues[i]!!) assign(a, namedValues[i]!!)
} else { } else {
val value = if (tp >= headPosBound) callArgs[tp--] val value = if (tp >= headPosBound) callArgs[tp--]
else a.defaultValue?.execute(scope) else missingValue(a, "too few arguments for the call")
?: scope.raiseIllegalArgument("too few arguments for the call")
assign(a, value) assign(a, value)
} }
i-- i--

View File

@ -31,6 +31,7 @@ sealed class CodeContext {
var typeParamDecls: List<TypeDecl.TypeParam> = emptyList() var typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
val pendingInitializations = mutableMapOf<String, Pos>() val pendingInitializations = mutableMapOf<String, Pos>()
val declaredMembers = mutableSetOf<String>() val declaredMembers = mutableSetOf<String>()
val classScopeMembers = mutableSetOf<String>()
val memberOverrides = mutableMapOf<String, Boolean>() val memberOverrides = mutableMapOf<String, Boolean>()
val memberFieldIds = mutableMapOf<String, Int>() val memberFieldIds = mutableMapOf<String, Int>()
val memberMethodIds = mutableMapOf<String, Int>() val memberMethodIds = mutableMapOf<String, Int>()

View File

@ -141,6 +141,8 @@ class Compiler(
private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf() private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
private val lambdaReturnTypeByRef: MutableMap<ObjRef, ObjClass> = mutableMapOf() private val lambdaReturnTypeByRef: MutableMap<ObjRef, ObjClass> = mutableMapOf()
private val classFieldTypesByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf() private val classFieldTypesByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf()
private val classScopeMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
private val classScopeCallableMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
private val encodedPayloadTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf() private val encodedPayloadTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf() private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
@ -330,10 +332,61 @@ class Compiler(
} }
} }
} }
"class", "object" -> { }
}
else -> {}
}
}
} finally {
cc.restorePos(saved)
}
}
private fun predeclareClassScopeMembers(
className: String,
target: MutableSet<String>,
callableTarget: MutableSet<String>
) {
val saved = cc.savePos()
var depth = 0
val modifiers = setOf(
"public", "private", "protected", "internal",
"override", "abstract", "extern", "static", "transient", "open", "closed"
)
fun nextNonWs(): Token {
var t = cc.next()
while (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) {
t = cc.next()
}
return t
}
try {
while (cc.hasNext()) {
var t = cc.next()
when (t.type) {
Token.Type.LBRACE -> depth++
Token.Type.RBRACE -> if (depth == 0) break else depth--
Token.Type.ID -> if (depth == 0) {
var sawStatic = false
while (t.type == Token.Type.ID && t.value in modifiers) {
if (t.value == "static") sawStatic = true
t = nextNonWs()
}
when (t.value) {
"class" -> {
val nameToken = nextNonWs() val nameToken = nextNonWs()
if (nameToken.type == Token.Type.ID) { if (nameToken.type == Token.Type.ID) {
target.add(nameToken.value) target.add(nameToken.value)
callableTarget.add(nameToken.value)
registerClassScopeMember(className, nameToken.value)
registerClassScopeCallableMember(className, nameToken.value)
}
}
"object" -> {
val nameToken = nextNonWs()
if (nameToken.type == Token.Type.ID) {
target.add(nameToken.value)
registerClassScopeMember(className, nameToken.value)
} }
} }
"enum" -> { "enum" -> {
@ -341,6 +394,25 @@ class Compiler(
val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next
if (nameToken.type == Token.Type.ID) { if (nameToken.type == Token.Type.ID) {
target.add(nameToken.value) target.add(nameToken.value)
callableTarget.add(nameToken.value)
registerClassScopeMember(className, nameToken.value)
registerClassScopeCallableMember(className, nameToken.value)
}
}
"type" -> {
val nameToken = nextNonWs()
if (nameToken.type == Token.Type.ID) {
target.add(nameToken.value)
registerClassScopeMember(className, nameToken.value)
}
}
"fun", "fn", "val", "var" -> {
if (sawStatic) {
val nameToken = nextNonWs()
if (nameToken.type == Token.Type.ID) {
target.add(nameToken.value)
registerClassScopeMember(className, nameToken.value)
}
} }
} }
} }
@ -353,6 +425,24 @@ class Compiler(
} }
} }
private fun registerClassScopeMember(className: String, name: String) {
classScopeMembersByClassName.getOrPut(className) { mutableSetOf() }.add(name)
}
private fun registerClassScopeCallableMember(className: String, name: String) {
classScopeCallableMembersByClassName.getOrPut(className) { mutableSetOf() }.add(name)
}
private fun isClassScopeCallableMember(className: String, name: String): Boolean {
return classScopeCallableMembersByClassName[className]?.contains(name) == true
}
private fun registerClassScopeFieldType(ownerClassName: String?, memberName: String, memberClassName: String) {
if (ownerClassName == null) return
val memberClass = resolveClassByName(memberClassName) ?: return
classFieldTypesByName.getOrPut(ownerClassName) { mutableMapOf() }[memberName] = memberClass
}
private fun resolveCompileClassInfo(name: String): CompileClassInfo? { private fun resolveCompileClassInfo(name: String): CompileClassInfo? {
compileClassInfos[name]?.let { return it } compileClassInfos[name]?.let { return it }
val scopeRec = seedScope?.get(name) ?: importManager.rootScope.get(name) val scopeRec = seedScope?.get(name) ?: importManager.rootScope.get(name)
@ -509,6 +599,11 @@ class Compiler(
return null return null
} }
private fun currentEnclosingClassName(): String? {
val ctx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
return ctx?.name
}
private fun currentTypeParams(): Set<String> { private fun currentTypeParams(): Set<String> {
val result = mutableSetOf<String>() val result = mutableSetOf<String>()
pendingTypeParamStack.lastOrNull()?.let { result.addAll(it) } pendingTypeParamStack.lastOrNull()?.let { result.addAll(it) }
@ -597,21 +692,20 @@ class Compiler(
} }
private suspend fun parseTypeAliasDeclaration(): Statement { private suspend fun parseTypeAliasDeclaration(): Statement {
if (codeContexts.lastOrNull() is CodeContext.ClassBody) {
throw ScriptError(cc.currentPos(), "type alias is not allowed in class body")
}
val nameToken = cc.requireToken(Token.Type.ID, "type alias name expected") val nameToken = cc.requireToken(Token.Type.ID, "type alias name expected")
val name = nameToken.value val declaredName = nameToken.value
if (typeAliases.containsKey(name)) { val outerClassName = currentEnclosingClassName()
throw ScriptError(nameToken.pos, "type alias $name already declared") val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
if (typeAliases.containsKey(qualifiedName)) {
throw ScriptError(nameToken.pos, "type alias $qualifiedName already declared")
} }
if (resolveTypeDeclObjClass(TypeDecl.Simple(name, false)) != null) { if (resolveTypeDeclObjClass(TypeDecl.Simple(qualifiedName, false)) != null) {
throw ScriptError(nameToken.pos, "type alias $name conflicts with existing class") throw ScriptError(nameToken.pos, "type alias $qualifiedName conflicts with existing class")
} }
val typeParams = parseTypeParamList() val typeParams = parseTypeParamList()
val uniqueParams = typeParams.map { it.name }.toSet() val uniqueParams = typeParams.map { it.name }.toSet()
if (uniqueParams.size != typeParams.size) { if (uniqueParams.size != typeParams.size) {
throw ScriptError(nameToken.pos, "type alias $name has duplicate type parameters") throw ScriptError(nameToken.pos, "type alias $qualifiedName has duplicate type parameters")
} }
val typeParamNames = uniqueParams val typeParamNames = uniqueParams
if (typeParamNames.isNotEmpty()) pendingTypeParamStack.add(typeParamNames) if (typeParamNames.isNotEmpty()) pendingTypeParamStack.add(typeParamNames)
@ -619,25 +713,30 @@ class Compiler(
cc.skipWsTokens() cc.skipWsTokens()
val eq = cc.nextNonWhitespace() val eq = cc.nextNonWhitespace()
if (eq.type != Token.Type.ASSIGN) { if (eq.type != Token.Type.ASSIGN) {
throw ScriptError(eq.pos, "type alias $name expects '='") throw ScriptError(eq.pos, "type alias $qualifiedName expects '='")
} }
parseTypeExpressionWithMini().first parseTypeExpressionWithMini().first
} finally { } finally {
if (typeParamNames.isNotEmpty()) pendingTypeParamStack.removeLast() if (typeParamNames.isNotEmpty()) pendingTypeParamStack.removeLast()
} }
val alias = TypeAliasDecl(name, typeParams, body, nameToken.pos) val alias = TypeAliasDecl(qualifiedName, typeParams, body, nameToken.pos)
typeAliases[name] = alias typeAliases[qualifiedName] = alias
declareLocalName(name, isMutable = false) declareLocalName(declaredName, isMutable = false)
resolutionSink?.declareSymbol(name, SymbolKind.LOCAL, isMutable = false, pos = nameToken.pos) resolutionSink?.declareSymbol(declaredName, SymbolKind.LOCAL, isMutable = false, pos = nameToken.pos)
if (outerClassName != null) {
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
outerCtx?.classScopeMembers?.add(declaredName)
registerClassScopeMember(outerClassName, declaredName)
}
pendingDeclDoc = null pendingDeclDoc = null
val aliasExpr = net.sergeych.lyng.obj.TypeDeclRef(body, nameToken.pos) val aliasExpr = net.sergeych.lyng.obj.TypeDeclRef(body, nameToken.pos)
val initStmt = ExpressionStatement(aliasExpr, nameToken.pos) val initStmt = ExpressionStatement(aliasExpr, nameToken.pos)
val slotPlan = slotPlanStack.lastOrNull() val slotPlan = slotPlanStack.lastOrNull()
val slotIndex = slotPlan?.slots?.get(name)?.index val slotIndex = slotPlan?.slots?.get(declaredName)?.index
val scopeId = slotPlan?.id val scopeId = slotPlan?.id
return VarDeclStatement( return VarDeclStatement(
name = name, name = declaredName,
isMutable = false, isMutable = false,
visibility = Visibility.Public, visibility = Visibility.Public,
initializer = initStmt, initializer = initStmt,
@ -749,6 +848,10 @@ class Compiler(
val ids = resolveImplicitThisMemberIds(name, pos, implicitType) val ids = resolveImplicitThisMemberIds(name, pos, implicitType)
return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType) return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, implicitType)
} }
if (classCtx != null && classCtx.classScopeMembers.contains(name)) {
resolutionSink?.referenceMember(name, pos, classCtx.name)
return ClassScopeMemberRef(name, pos, classCtx.name)
}
val modulePlan = moduleSlotPlan() val modulePlan = moduleSlotPlan()
val moduleEntry = modulePlan?.slots?.get(name) val moduleEntry = modulePlan?.slots?.get(name)
if (moduleEntry != null) { if (moduleEntry != null) {
@ -1634,6 +1737,7 @@ class Compiler(
} }
is QualifiedThisMethodSlotCallRef -> true is QualifiedThisMethodSlotCallRef -> true
is QualifiedThisFieldSlotRef -> true is QualifiedThisFieldSlotRef -> true
is ClassScopeMemberRef -> true
else -> false else -> false
} }
} }
@ -2015,6 +2119,14 @@ class Compiler(
when (nt.type) { when (nt.type) {
Token.Type.LPAREN -> { Token.Type.LPAREN -> {
cc.next() cc.next()
if (shouldTreatAsClassScopeCall(left, next.value)) {
val parsed = parseArgs(null)
val args = parsed.first
val tailBlock = parsed.second
isCall = true
val field = FieldRef(left, next.value, isOptional)
operand = CallRef(field, args, tailBlock, isOptional)
} else {
// instance method call // instance method call
val receiverType = if (next.value == "apply" || next.value == "run") { val receiverType = if (next.value == "apply" || next.value == "run") {
inferReceiverTypeFromRef(left) inferReceiverTypeFromRef(left)
@ -2068,6 +2180,7 @@ class Compiler(
} }
} }
} }
}
Token.Type.LBRACE, Token.Type.NULL_COALESCE_BLOCKINVOKE -> { Token.Type.LBRACE, Token.Type.NULL_COALESCE_BLOCKINVOKE -> {
@ -2872,7 +2985,15 @@ class Compiler(
pos: Pos, pos: Pos,
seen: MutableSet<String> seen: MutableSet<String>
): TypeDecl? { ): TypeDecl? {
val alias = typeAliases[name] ?: typeAliases[name.substringAfterLast('.')] ?: return null val alias = run {
if (!name.contains('.')) {
val classCtx = currentEnclosingClassName()
if (classCtx != null) {
typeAliases["$classCtx.$name"]?.let { return@run it }
}
}
typeAliases[name] ?: typeAliases[name.substringAfterLast('.')]
} ?: return null
if (!seen.add(alias.name)) throw ScriptError(pos, "circular type alias: ${alias.name}") if (!seen.add(alias.name)) throw ScriptError(pos, "circular type alias: ${alias.name}")
val bindings = buildTypeAliasBindings(alias, args, pos) val bindings = buildTypeAliasBindings(alias, args, pos)
val substituted = substituteTypeAliasTypeVars(alias.body, bindings) val substituted = substituteTypeAliasTypeVars(alias.body, bindings)
@ -3534,6 +3655,10 @@ class Compiler(
?: nameObjClass[ref.name] ?: nameObjClass[ref.name]
?: resolveClassByName(ref.name) ?: resolveClassByName(ref.name)
} }
is ClassScopeMemberRef -> {
val targetClass = resolveClassByName(ref.ownerClassName()) ?: return null
inferFieldReturnClass(targetClass, ref.name)
}
is ListLiteralRef -> ObjList.type is ListLiteralRef -> ObjList.type
is MapLiteralRef -> ObjMap.type is MapLiteralRef -> ObjMap.type
is RangeRef -> ObjRange.type is RangeRef -> ObjRange.type
@ -3568,6 +3693,10 @@ class Compiler(
is LocalVarRef -> nameObjClass[ref.name] is LocalVarRef -> nameObjClass[ref.name]
?: nameTypeDecl[ref.name]?.let { resolveTypeDeclObjClass(it) } ?: nameTypeDecl[ref.name]?.let { resolveTypeDeclObjClass(it) }
?: resolveClassByName(ref.name) ?: resolveClassByName(ref.name)
is ClassScopeMemberRef -> {
val targetClass = resolveClassByName(ref.ownerClassName())
inferFieldReturnClass(targetClass, ref.name)
}
is ImplicitThisMemberRef -> { is ImplicitThisMemberRef -> {
val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName() val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName()
val targetClass = typeName?.let { resolveClassByName(it) } val targetClass = typeName?.let { resolveClassByName(it) }
@ -3632,6 +3761,11 @@ class Compiler(
is ObjString -> ObjString.type is ObjString -> ObjString.type
else -> null else -> null
} }
is FieldRef -> {
val receiverClass = resolveReceiverClassForMember(target.target) ?: return null
if (!isClassScopeCallableMember(receiverClass.className, target.name)) return null
resolveClassByName("${receiverClass.className}.${target.name}")
}
else -> null else -> null
} }
} }
@ -3828,6 +3962,11 @@ class Compiler(
return null return null
} }
private fun shouldTreatAsClassScopeCall(left: ObjRef, memberName: String): Boolean {
val receiverClass = resolveReceiverClassForMember(left) ?: return false
return isClassScopeCallableMember(receiverClass.className, memberName)
}
private fun enforceReceiverTypeForMember(left: ObjRef, memberName: String, pos: Pos) { private fun enforceReceiverTypeForMember(left: ObjRef, memberName: String, pos: Pos) {
if (left is LocalVarRef && left.name == "scope") return if (left is LocalVarRef && left.name == "scope") return
if (left is LocalSlotRef && left.name == "scope") return if (left is LocalSlotRef && left.name == "scope") return
@ -5098,7 +5237,22 @@ class Compiler(
val doc = pendingDeclDoc ?: consumePendingDoc() val doc = pendingDeclDoc ?: consumePendingDoc()
pendingDeclDoc = null pendingDeclDoc = null
pendingDeclStart = null pendingDeclStart = null
resolutionSink?.declareSymbol(nameToken.value, SymbolKind.ENUM, isMutable = false, pos = nameToken.pos) val declaredName = nameToken.value
val outerClassName = currentEnclosingClassName()
val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
resolutionSink?.declareSymbol(declaredName, SymbolKind.ENUM, isMutable = false, pos = nameToken.pos)
if (outerClassName != null) {
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
outerCtx?.classScopeMembers?.add(declaredName)
registerClassScopeMember(outerClassName, declaredName)
registerClassScopeCallableMember(outerClassName, declaredName)
}
val lifted = if (cc.peekNextNonWhitespace().type == Token.Type.STAR) {
cc.nextNonWhitespace()
true
} else {
false
}
// so far only simplest enums: // so far only simplest enums:
val names = mutableListOf<String>() val names = mutableListOf<String>()
val positions = mutableListOf<Pos>() val positions = mutableListOf<Pos>()
@ -5134,7 +5288,7 @@ class Compiler(
miniSink?.onEnumDecl( miniSink?.onEnumDecl(
MiniEnumDecl( MiniEnumDecl(
range = MiniRange(startPos, cc.currentPos()), range = MiniRange(startPos, cc.currentPos()),
name = nameToken.value, name = declaredName,
entries = names, entries = names,
doc = doc, doc = doc,
nameStart = nameToken.pos, nameStart = nameToken.pos,
@ -5142,28 +5296,69 @@ class Compiler(
entryPositions = positions entryPositions = positions
) )
) )
if (lifted) {
val classCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
val conflicts = when {
classCtx != null -> names.filter { entry ->
classCtx.classScopeMembers.contains(entry) || classCtx.declaredMembers.contains(entry)
}
else -> {
val modulePlan = moduleSlotPlan()
names.filter { entry -> modulePlan?.slots?.containsKey(entry) == true }
}
}
if (conflicts.isNotEmpty()) {
val entry = conflicts.first()
val disambiguation = "${qualifiedName}.$entry"
throw ScriptError(
nameToken.pos,
"lifted enum entry '$entry' conflicts with existing member; use '$disambiguation'"
)
}
}
val fieldIds = LinkedHashMap<String, Int>(names.size + 1) val fieldIds = LinkedHashMap<String, Int>(names.size + 1)
fieldIds["entries"] = 0 fieldIds["entries"] = 0
for ((index, entry) in names.withIndex()) { for ((index, entry) in names.withIndex()) {
fieldIds[entry] = index + 1 fieldIds[entry] = index + 1
} }
val methodIds = mapOf("valueOf" to 0) val methodIds = mapOf("valueOf" to 0)
compileClassInfos[nameToken.value] = CompileClassInfo( compileClassInfos[qualifiedName] = CompileClassInfo(
name = nameToken.value, name = qualifiedName,
fieldIds = fieldIds, fieldIds = fieldIds,
methodIds = methodIds, methodIds = methodIds,
nextFieldId = fieldIds.size, nextFieldId = fieldIds.size,
nextMethodId = methodIds.size, nextMethodId = methodIds.size,
baseNames = listOf("Object") baseNames = listOf("Object")
) )
enumEntriesByName[nameToken.value] = names.toList() enumEntriesByName[qualifiedName] = names.toList()
registerClassScopeFieldType(outerClassName, declaredName, qualifiedName)
if (lifted && outerClassName != null) {
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
for (entry in names) {
outerCtx?.classScopeMembers?.add(entry)
registerClassScopeMember(outerClassName, entry)
registerClassScopeFieldType(outerClassName, entry, qualifiedName)
}
} else if (lifted) {
for (entry in names) {
declareLocalName(entry, isMutable = false)
}
}
val stmtPos = startPos val stmtPos = startPos
val enumDeclStatement = object : Statement() { val enumDeclStatement = object : Statement() {
override val pos: Pos = stmtPos override val pos: Pos = stmtPos
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val enumClass = ObjEnumClass.createSimpleEnum(nameToken.value, names) val enumClass = ObjEnumClass.createSimpleEnum(qualifiedName, names)
scope.addItem(nameToken.value, false, enumClass, recordType = ObjRecord.Type.Enum) scope.addItem(declaredName, false, enumClass, recordType = ObjRecord.Type.Enum)
if (lifted) {
for (entry in names) {
val rec = enumClass.getInstanceMemberOrNull(entry, includeAbstract = false, includeStatic = true)
if (rec != null) {
scope.addItem(entry, false, rec.value)
}
}
}
return enumClass return enumClass
} }
} }
@ -5175,10 +5370,18 @@ class Compiler(
val nameToken = if (next.type == Token.Type.ID) cc.requireToken(Token.Type.ID) else null val nameToken = if (next.type == Token.Type.ID) cc.requireToken(Token.Type.ID) else null
val startPos = pendingDeclStart ?: nameToken?.pos ?: cc.current().pos val startPos = pendingDeclStart ?: nameToken?.pos ?: cc.current().pos
val className = nameToken?.value ?: generateAnonName(startPos) val declaredName = nameToken?.value
if (nameToken != null) { val outerClassName = currentEnclosingClassName()
resolutionSink?.declareSymbol(nameToken.value, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos) val baseName = declaredName ?: generateAnonName(startPos)
declareLocalName(nameToken.value, isMutable = false) val className = if (declaredName != null && outerClassName != null) "$outerClassName.$declaredName" else baseName
if (declaredName != null) {
resolutionSink?.declareSymbol(declaredName, SymbolKind.CLASS, isMutable = false, pos = nameToken!!.pos)
declareLocalName(declaredName, isMutable = false)
if (outerClassName != null) {
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
outerCtx?.classScopeMembers?.add(declaredName)
registerClassScopeMember(outerClassName, declaredName)
}
} }
val doc = pendingDeclDoc ?: consumePendingDoc() val doc = pendingDeclDoc ?: consumePendingDoc()
@ -5215,7 +5418,7 @@ class Compiler(
run { run {
val node = MiniClassDecl( val node = MiniClassDecl(
range = MiniRange(startPos, cc.currentPos()), range = MiniRange(startPos, cc.currentPos()),
name = className, name = declaredName ?: className,
bases = baseSpecs.map { it.name }, bases = baseSpecs.map { it.name },
bodyRange = null, bodyRange = null,
doc = doc, doc = doc,
@ -5232,6 +5435,8 @@ class Compiler(
resolutionSink?.enterScope(ScopeKind.CLASS, startPos, className, baseSpecs.map { it.name }) resolutionSink?.enterScope(ScopeKind.CLASS, startPos, className, baseSpecs.map { it.name })
val classCtx = codeContexts.lastOrNull() as? CodeContext.ClassBody val classCtx = codeContexts.lastOrNull() as? CodeContext.ClassBody
classCtx?.let { ctx -> classCtx?.let { ctx ->
val callableMembers = classScopeCallableMembersByClassName.getOrPut(className) { mutableSetOf() }
predeclareClassScopeMembers(className, ctx.classScopeMembers, callableMembers)
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name }) val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
ctx.memberFieldIds.putAll(baseIds.fieldIds) ctx.memberFieldIds.putAll(baseIds.fieldIds)
ctx.memberMethodIds.putAll(baseIds.methodIds) ctx.memberMethodIds.putAll(baseIds.methodIds)
@ -5254,6 +5459,9 @@ class Compiler(
) )
} }
} }
if (declaredName != null) {
registerClassScopeFieldType(outerClassName, declaredName, className)
}
parsed parsed
} finally { } finally {
slotPlanStack.removeLast() slotPlanStack.removeLast()
@ -5269,7 +5477,7 @@ class Compiler(
run { run {
val node = MiniClassDecl( val node = MiniClassDecl(
range = MiniRange(startPos, cc.currentPos()), range = MiniRange(startPos, cc.currentPos()),
name = className, name = declaredName ?: className,
bases = baseSpecs.map { it.name }, bases = baseSpecs.map { it.name },
bodyRange = null, bodyRange = null,
doc = doc, doc = doc,
@ -5291,6 +5499,9 @@ class Compiler(
baseNames = baseSpecs.map { it.name } baseNames = baseSpecs.map { it.name }
) )
} }
if (declaredName != null) {
registerClassScopeFieldType(outerClassName, declaredName, className)
}
cc.restorePos(saved) cc.restorePos(saved)
null null
} }
@ -5324,8 +5535,8 @@ class Compiler(
// Create instance (singleton) // Create instance (singleton)
val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY)) val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY))
if (nameToken != null) if (declaredName != null)
scope.addItem(className, false, instance) scope.addItem(declaredName, false, instance)
return instance return instance
} }
} }
@ -5338,8 +5549,17 @@ class Compiler(
val doc = pendingDeclDoc ?: consumePendingDoc() val doc = pendingDeclDoc ?: consumePendingDoc()
pendingDeclDoc = null pendingDeclDoc = null
pendingDeclStart = null pendingDeclStart = null
resolutionSink?.declareSymbol(nameToken.value, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos) val declaredName = nameToken.value
return inCodeContext(CodeContext.ClassBody(nameToken.value, isExtern = isExtern)) { val outerClassName = currentEnclosingClassName()
val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
resolutionSink?.declareSymbol(declaredName, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
if (outerClassName != null) {
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
outerCtx?.classScopeMembers?.add(declaredName)
registerClassScopeMember(outerClassName, declaredName)
registerClassScopeCallableMember(outerClassName, declaredName)
}
return inCodeContext(CodeContext.ClassBody(qualifiedName, isExtern = isExtern)) {
val classCtx = codeContexts.lastOrNull() as? CodeContext.ClassBody val classCtx = codeContexts.lastOrNull() as? CodeContext.ClassBody
val typeParamDecls = parseTypeParamList() val typeParamDecls = parseTypeParamList()
classCtx?.typeParamDecls = typeParamDecls classCtx?.typeParamDecls = typeParamDecls
@ -5366,7 +5586,7 @@ class Compiler(
if (param.accessType != null) { if (param.accessType != null) {
val declClass = resolveTypeDeclObjClass(param.type) val declClass = resolveTypeDeclObjClass(param.type)
if (declClass != null) { if (declClass != null) {
classFieldTypesByName.getOrPut(nameToken.value) { mutableMapOf() }[param.name] = declClass classFieldTypesByName.getOrPut(qualifiedName) { mutableMapOf() }[param.name] = declClass
} }
} }
} }
@ -5440,7 +5660,7 @@ class Compiler(
run { run {
val node = MiniClassDecl( val node = MiniClassDecl(
range = MiniRange(startPos, cc.currentPos()), range = MiniRange(startPos, cc.currentPos()),
name = nameToken.value, name = declaredName,
bases = baseSpecs.map { it.name }, bases = baseSpecs.map { it.name },
bodyRange = null, bodyRange = null,
ctorFields = ctorFields, ctorFields = ctorFields,
@ -5453,8 +5673,8 @@ class Compiler(
// parse body // parse body
val bodyStart = next.pos val bodyStart = next.pos
slotPlanStack.add(classSlotPlan) slotPlanStack.add(classSlotPlan)
resolutionSink?.declareClass(nameToken.value, baseSpecs.map { it.name }, startPos) resolutionSink?.declareClass(qualifiedName, baseSpecs.map { it.name }, startPos)
resolutionSink?.enterScope(ScopeKind.CLASS, startPos, nameToken.value, baseSpecs.map { it.name }) resolutionSink?.enterScope(ScopeKind.CLASS, startPos, qualifiedName, baseSpecs.map { it.name })
constructorArgsDeclaration?.params?.forEach { param -> constructorArgsDeclaration?.params?.forEach { param ->
val accessType = param.accessType val accessType = param.accessType
val kind = if (accessType != null) SymbolKind.MEMBER else SymbolKind.PARAM val kind = if (accessType != null) SymbolKind.MEMBER else SymbolKind.PARAM
@ -5463,8 +5683,10 @@ class Compiler(
} }
val st = try { val st = try {
classCtx?.let { ctx -> classCtx?.let { ctx ->
val callableMembers = classScopeCallableMembersByClassName.getOrPut(qualifiedName) { mutableSetOf() }
predeclareClassScopeMembers(qualifiedName, ctx.classScopeMembers, callableMembers)
predeclareClassMembers(ctx.declaredMembers, ctx.memberOverrides) predeclareClassMembers(ctx.declaredMembers, ctx.memberOverrides)
val existingExternInfo = if (isExtern) resolveCompileClassInfo(nameToken.value) else null val existingExternInfo = if (isExtern) resolveCompileClassInfo(qualifiedName) else null
if (existingExternInfo != null) { if (existingExternInfo != null) {
ctx.memberFieldIds.putAll(existingExternInfo.fieldIds) ctx.memberFieldIds.putAll(existingExternInfo.fieldIds)
ctx.memberMethodIds.putAll(existingExternInfo.methodIds) ctx.memberMethodIds.putAll(existingExternInfo.methodIds)
@ -5474,18 +5696,18 @@ class Compiler(
val hasField = member in existingExternInfo.fieldIds val hasField = member in existingExternInfo.fieldIds
val hasMethod = member in existingExternInfo.methodIds val hasMethod = member in existingExternInfo.methodIds
if (!hasField && !hasMethod) { if (!hasField && !hasMethod) {
throw ScriptError(nameToken.pos, "extern member $member is not found in runtime class ${nameToken.value}") throw ScriptError(nameToken.pos, "extern member $member is not found in runtime class $qualifiedName")
} }
} }
constructorArgsDeclaration?.params?.forEach { param -> constructorArgsDeclaration?.params?.forEach { param ->
if (param.accessType == null) return@forEach if (param.accessType == null) return@forEach
if (!ctx.memberFieldIds.containsKey(param.name)) { if (!ctx.memberFieldIds.containsKey(param.name)) {
val fieldId = existingExternInfo.fieldIds[param.name] val fieldId = existingExternInfo.fieldIds[param.name]
?: throw ScriptError(nameToken.pos, "extern field ${param.name} is not found in runtime class ${nameToken.value}") ?: throw ScriptError(nameToken.pos, "extern field ${param.name} is not found in runtime class $qualifiedName")
ctx.memberFieldIds[param.name] = fieldId ctx.memberFieldIds[param.name] = fieldId
} }
} }
compileClassInfos[nameToken.value] = existingExternInfo compileClassInfos[qualifiedName] = existingExternInfo
} else { } else {
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name }) val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
ctx.memberFieldIds.putAll(baseIds.fieldIds) ctx.memberFieldIds.putAll(baseIds.fieldIds)
@ -5512,8 +5734,8 @@ class Compiler(
ctx.memberFieldIds[param.name] = ctx.nextFieldId++ ctx.memberFieldIds[param.name] = ctx.nextFieldId++
} }
} }
compileClassInfos[nameToken.value] = CompileClassInfo( compileClassInfos[qualifiedName] = CompileClassInfo(
name = nameToken.value, name = qualifiedName,
fieldIds = ctx.memberFieldIds.toMap(), fieldIds = ctx.memberFieldIds.toMap(),
methodIds = ctx.memberMethodIds.toMap(), methodIds = ctx.memberMethodIds.toMap(),
nextFieldId = ctx.nextFieldId, nextFieldId = ctx.nextFieldId,
@ -5527,8 +5749,8 @@ class Compiler(
} }
if (!isExtern) { if (!isExtern) {
classCtx?.let { ctx -> classCtx?.let { ctx ->
compileClassInfos[nameToken.value] = CompileClassInfo( compileClassInfos[qualifiedName] = CompileClassInfo(
name = nameToken.value, name = qualifiedName,
fieldIds = ctx.memberFieldIds.toMap(), fieldIds = ctx.memberFieldIds.toMap(),
methodIds = ctx.memberMethodIds.toMap(), methodIds = ctx.memberMethodIds.toMap(),
nextFieldId = ctx.nextFieldId, nextFieldId = ctx.nextFieldId,
@ -5537,6 +5759,7 @@ class Compiler(
) )
} }
} }
registerClassScopeFieldType(outerClassName, declaredName, qualifiedName)
parsed parsed
} finally { } finally {
slotPlanStack.removeLast() slotPlanStack.removeLast()
@ -5552,7 +5775,7 @@ class Compiler(
run { run {
val node = MiniClassDecl( val node = MiniClassDecl(
range = MiniRange(startPos, cc.currentPos()), range = MiniRange(startPos, cc.currentPos()),
name = nameToken.value, name = declaredName,
bases = baseSpecs.map { it.name }, bases = baseSpecs.map { it.name },
bodyRange = null, bodyRange = null,
ctorFields = ctorFields, ctorFields = ctorFields,
@ -5562,9 +5785,9 @@ class Compiler(
) )
miniSink?.onClassDecl(node) miniSink?.onClassDecl(node)
} }
resolutionSink?.declareClass(nameToken.value, baseSpecs.map { it.name }, startPos) resolutionSink?.declareClass(qualifiedName, baseSpecs.map { it.name }, startPos)
classCtx?.let { ctx -> classCtx?.let { ctx ->
val existingExternInfo = if (isExtern) resolveCompileClassInfo(nameToken.value) else null val existingExternInfo = if (isExtern) resolveCompileClassInfo(qualifiedName) else null
if (existingExternInfo != null) { if (existingExternInfo != null) {
ctx.memberFieldIds.putAll(existingExternInfo.fieldIds) ctx.memberFieldIds.putAll(existingExternInfo.fieldIds)
ctx.memberMethodIds.putAll(existingExternInfo.methodIds) ctx.memberMethodIds.putAll(existingExternInfo.methodIds)
@ -5574,18 +5797,18 @@ class Compiler(
val hasField = member in existingExternInfo.fieldIds val hasField = member in existingExternInfo.fieldIds
val hasMethod = member in existingExternInfo.methodIds val hasMethod = member in existingExternInfo.methodIds
if (!hasField && !hasMethod) { if (!hasField && !hasMethod) {
throw ScriptError(nameToken.pos, "extern member $member is not found in runtime class ${nameToken.value}") throw ScriptError(nameToken.pos, "extern member $member is not found in runtime class $qualifiedName")
} }
} }
constructorArgsDeclaration?.params?.forEach { param -> constructorArgsDeclaration?.params?.forEach { param ->
if (param.accessType == null) return@forEach if (param.accessType == null) return@forEach
if (!ctx.memberFieldIds.containsKey(param.name)) { if (!ctx.memberFieldIds.containsKey(param.name)) {
val fieldId = existingExternInfo.fieldIds[param.name] val fieldId = existingExternInfo.fieldIds[param.name]
?: throw ScriptError(nameToken.pos, "extern field ${param.name} is not found in runtime class ${nameToken.value}") ?: throw ScriptError(nameToken.pos, "extern field ${param.name} is not found in runtime class $qualifiedName")
ctx.memberFieldIds[param.name] = fieldId ctx.memberFieldIds[param.name] = fieldId
} }
} }
compileClassInfos[nameToken.value] = existingExternInfo compileClassInfos[qualifiedName] = existingExternInfo
} else { } else {
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name }) val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
ctx.memberFieldIds.putAll(baseIds.fieldIds) ctx.memberFieldIds.putAll(baseIds.fieldIds)
@ -5612,8 +5835,8 @@ class Compiler(
ctx.memberFieldIds[param.name] = ctx.nextFieldId++ ctx.memberFieldIds[param.name] = ctx.nextFieldId++
} }
} }
compileClassInfos[nameToken.value] = CompileClassInfo( compileClassInfos[qualifiedName] = CompileClassInfo(
name = nameToken.value, name = qualifiedName,
fieldIds = ctx.memberFieldIds.toMap(), fieldIds = ctx.memberFieldIds.toMap(),
methodIds = ctx.memberMethodIds.toMap(), methodIds = ctx.memberMethodIds.toMap(),
nextFieldId = ctx.nextFieldId, nextFieldId = ctx.nextFieldId,
@ -5622,6 +5845,7 @@ class Compiler(
) )
} }
} }
registerClassScopeFieldType(outerClassName, declaredName, qualifiedName)
// restore if no body starts here // restore if no body starts here
cc.restorePos(saved) cc.restorePos(saved)
null null
@ -5631,7 +5855,7 @@ class Compiler(
val initScope = popInitScope() val initScope = popInitScope()
// create class // create class
val className = nameToken.value val className = qualifiedName
// @Suppress("UNUSED_VARIABLE") val defaultAccess = if (isStruct) AccessType.Variable else AccessType.Initialization // @Suppress("UNUSED_VARIABLE") val defaultAccess = if (isStruct) AccessType.Variable else AccessType.Initialization
// @Suppress("UNUSED_VARIABLE") val defaultVisibility = Visibility.Public // @Suppress("UNUSED_VARIABLE") val defaultVisibility = Visibility.Public
@ -5698,7 +5922,7 @@ class Compiler(
} }
} }
scope.addItem(className, false, newClass) scope.addItem(declaredName, false, newClass)
// Prepare class scope for class-scope members (static) and future registrations // Prepare class scope for class-scope members (static) and future registrations
val classScope = scope.createChildScope(newThisObj = newClass) val classScope = scope.createChildScope(newThisObj = newClass)
// Set lexical class context for visibility tagging inside class body // Set lexical class context for visibility tagging inside class body
@ -5715,7 +5939,7 @@ class Compiler(
return newClass return newClass
} }
} }
ClassDeclStatement(classDeclStatement, startPos, nameToken.value) ClassDeclStatement(classDeclStatement, startPos, qualifiedName)
} }
} }
@ -6721,6 +6945,11 @@ class Compiler(
target is ImplicitThisMemberRef && target.name == "iterator" -> ObjIterator target is ImplicitThisMemberRef && target.name == "iterator" -> ObjIterator
target is ThisFieldSlotRef && target.name == "iterator" -> ObjIterator target is ThisFieldSlotRef && target.name == "iterator" -> ObjIterator
target is FieldRef && target.name == "iterator" -> ObjIterator target is FieldRef && target.name == "iterator" -> ObjIterator
target is FieldRef -> {
val receiverClass = resolveReceiverClassForMember(target.target) ?: return null
if (!isClassScopeCallableMember(receiverClass.className, target.name)) return null
resolveClassByName("${receiverClass.className}.${target.name}")
}
target is ConstRef -> when (val value = target.constValue) { target is ConstRef -> when (val value = target.constValue) {
is ObjClass -> value is ObjClass -> value
is ObjString -> ObjString.type is ObjString -> ObjString.type
@ -6774,6 +7003,12 @@ class Compiler(
else -> return null else -> return null
} }
val name = rawName.substringAfterLast('.') val name = rawName.substringAfterLast('.')
if (!rawName.contains('.')) {
val classCtx = currentEnclosingClassName()
if (classCtx != null) {
resolveClassByName("$classCtx.$rawName")?.let { return it }
}
}
return when (name) { return when (name) {
"Object", "Obj" -> Obj.rootObjectType "Object", "Obj" -> Obj.rootObjectType
"String" -> ObjString.type "String" -> ObjString.type

View File

@ -242,6 +242,7 @@ private fun applyEnumConstantHeuristics(
var j = i + 1 var j = i + 1
// skip optional whitespace/newlines tokens are separate types, so we just check IDs and braces // skip optional whitespace/newlines tokens are separate types, so we just check IDs and braces
if (j < tokens.size && tokens[j].type == Type.ID) j++ else { i++; continue } if (j < tokens.size && tokens[j].type == Type.ID) j++ else { i++; continue }
if (j < tokens.size && tokens[j].type == Type.STAR) j++
if (j < tokens.size && tokens[j].type == Type.LBRACE) { if (j < tokens.size && tokens[j].type == Type.LBRACE) {
j++ j++
while (j < tokens.size) { while (j < tokens.size) {

View File

@ -2133,6 +2133,50 @@ class ImplicitThisMemberRef(
} }
} }
/**
* Reference to a class-scope member in the nearest enclosing class context.
*/
class ClassScopeMemberRef(
val name: String,
private val atPos: Pos,
private val ownerClassName: String
) : ObjRef {
internal fun ownerClassName(): String = ownerClassName
override fun forEachVariable(block: (String) -> Unit) {
block(name)
}
override fun forEachVariableWithPos(block: (String, Pos) -> Unit) {
block(name, atPos)
}
private fun resolveClass(scope: Scope): ObjClass {
scope.thisVariants.firstOrNull { it is ObjClass && it.className == ownerClassName }?.let {
return it as ObjClass
}
val cls = scope[ownerClassName]?.value as? ObjClass
if (cls != null) return cls
scope.raiseSymbolNotFound(ownerClassName)
}
override suspend fun get(scope: Scope): ObjRecord {
scope.pos = atPos
val cls = resolveClass(scope)
return cls.readField(scope, name)
}
override suspend fun evalValue(scope: Scope): Obj {
val rec = get(scope)
return scope.resolve(rec, name)
}
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
scope.pos = atPos
val cls = resolveClass(scope)
cls.writeField(scope, name, newValue)
}
}
/** /**
* Fast path for implicit member calls in class bodies: `foo(...)` resolves locals first, * Fast path for implicit member calls in class bodies: `foo(...)` resolves locals first,
* then falls back to member lookup on `this`. * then falls back to member lookup on `this`.

View File

@ -3545,6 +3545,45 @@ class ScriptTest {
) )
} }
@Test
fun nestedTypesAndObjects() = runTest {
eval(
"""
class A {
class B(x?)
object Inner { val foo = "bar" }
}
val ab = A.B()
assertEquals(ab.x, null)
assertEquals(A.Inner.foo, "bar")
""".trimIndent()
)
}
@Test
fun liftedEnumEntries() = runTest {
eval(
"""
class A {
enum E* { One, Two }
}
assertEquals(A.One, A.E.One)
assertEquals(A.Two, A.E.Two)
""".trimIndent()
)
assertFailsWith<ScriptError> {
eval(
"""
class A {
val One = 1
enum E* { One, Two }
}
""".trimIndent()
)
}
}
@Test @Test
fun enumSerializationTest() = runTest { fun enumSerializationTest() = runTest {
eval( eval(

View File

@ -75,6 +75,18 @@ class B { fun foo() = 2 }
class C : A, B { } // error: requires override class C : A, B { } // error: requires override
``` ```
## Class Namespace (Nested Declarations)
Nested classes, objects, enums, and type aliases belong to the **class namespace** of their enclosing class. They are not instance members and do not capture an outer instance.
Resolution rules:
- Qualified access (`Outer.Inner`) resolves to a class-namespace member at compile time.
- Unqualified access inside `Outer` can resolve to nested declarations if not shadowed by locals/params.
- Class-namespace members are never resolved via runtime name lookup; failures are compile-time errors.
Enum lifting:
- `enum E* { ... }` lifts entries into the enclosing class namespace (e.g., `Outer.Entry`).
- Any ambiguity with existing class members is a compile-time error.
## Shadowing Rules ## Shadowing Rules
Shadowing policy is configurable: Shadowing policy is configurable:
- Locals may shadow parameters (allowed by default). - Locals may shadow parameters (allowed by default).