Add nested declarations and lifted enums
This commit is contained in:
parent
c35efdc2ae
commit
3b290116b8
10
README.md
10
README.md
@ -25,6 +25,16 @@ Point(x:, y:).dist() //< 5
|
||||
fun swapEnds(first, args..., last, f) {
|
||||
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)
|
||||
|
||||
42
docs/OOP.md
42
docs/OOP.md
@ -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.
|
||||
- **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 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.
|
||||
|
||||
@ -107,6 +107,23 @@ Singleton objects are declared using the `object` keyword. They define a class a
|
||||
|
||||
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)
|
||||
|
||||
You can delegate properties and functions to other objects using the `by` keyword. This is perfect for patterns like `lazy` initialization.
|
||||
|
||||
@ -108,6 +108,24 @@ object Config {
|
||||
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
|
||||
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.
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ package net.sergeych.lyng
|
||||
import net.sergeych.lyng.miniast.MiniTypeRef
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjList
|
||||
import net.sergeych.lyng.obj.ObjNull
|
||||
import net.sergeych.lyng.obj.ObjRecord
|
||||
|
||||
/**
|
||||
@ -61,12 +62,20 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
}
|
||||
}
|
||||
if (!hasComplex) {
|
||||
if (arguments.list.size != params.size)
|
||||
if (arguments.list.size > params.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) {
|
||||
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) {
|
||||
ObjRecord.Type.ConstructorField
|
||||
} 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
|
||||
val callArgs: List<Obj>
|
||||
val paramsSize: Int
|
||||
@ -181,8 +195,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
assign(a, namedValues[i]!!)
|
||||
} else {
|
||||
val value = if (hp < callArgs.size) callArgs[hp++]
|
||||
else a.defaultValue?.execute(scope)
|
||||
?: scope.raiseIllegalArgument("too few arguments for the call (missing ${a.name})")
|
||||
else missingValue(a, "too few arguments for the call (missing ${a.name})")
|
||||
assign(a, value)
|
||||
}
|
||||
i++
|
||||
@ -202,8 +215,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
assign(a, namedValues[i]!!)
|
||||
} else {
|
||||
val value = if (tp >= headPosBound) callArgs[tp--]
|
||||
else a.defaultValue?.execute(scope)
|
||||
?: scope.raiseIllegalArgument("too few arguments for the call")
|
||||
else missingValue(a, "too few arguments for the call")
|
||||
assign(a, value)
|
||||
}
|
||||
i--
|
||||
|
||||
@ -31,6 +31,7 @@ sealed class CodeContext {
|
||||
var typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
|
||||
val pendingInitializations = mutableMapOf<String, Pos>()
|
||||
val declaredMembers = mutableSetOf<String>()
|
||||
val classScopeMembers = mutableSetOf<String>()
|
||||
val memberOverrides = mutableMapOf<String, Boolean>()
|
||||
val memberFieldIds = mutableMapOf<String, Int>()
|
||||
val memberMethodIds = mutableMapOf<String, Int>()
|
||||
|
||||
@ -141,6 +141,8 @@ class Compiler(
|
||||
private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
||||
private val lambdaReturnTypeByRef: MutableMap<ObjRef, 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 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()
|
||||
if (nameToken.type == Token.Type.ID) {
|
||||
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" -> {
|
||||
@ -341,6 +394,25 @@ class Compiler(
|
||||
val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next
|
||||
if (nameToken.type == Token.Type.ID) {
|
||||
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? {
|
||||
compileClassInfos[name]?.let { return it }
|
||||
val scopeRec = seedScope?.get(name) ?: importManager.rootScope.get(name)
|
||||
@ -509,6 +599,11 @@ class Compiler(
|
||||
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> {
|
||||
val result = mutableSetOf<String>()
|
||||
pendingTypeParamStack.lastOrNull()?.let { result.addAll(it) }
|
||||
@ -597,21 +692,20 @@ class Compiler(
|
||||
}
|
||||
|
||||
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 name = nameToken.value
|
||||
if (typeAliases.containsKey(name)) {
|
||||
throw ScriptError(nameToken.pos, "type alias $name already declared")
|
||||
val declaredName = nameToken.value
|
||||
val outerClassName = currentEnclosingClassName()
|
||||
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) {
|
||||
throw ScriptError(nameToken.pos, "type alias $name conflicts with existing class")
|
||||
if (resolveTypeDeclObjClass(TypeDecl.Simple(qualifiedName, false)) != null) {
|
||||
throw ScriptError(nameToken.pos, "type alias $qualifiedName conflicts with existing class")
|
||||
}
|
||||
val typeParams = parseTypeParamList()
|
||||
val uniqueParams = typeParams.map { it.name }.toSet()
|
||||
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
|
||||
if (typeParamNames.isNotEmpty()) pendingTypeParamStack.add(typeParamNames)
|
||||
@ -619,25 +713,30 @@ class Compiler(
|
||||
cc.skipWsTokens()
|
||||
val eq = cc.nextNonWhitespace()
|
||||
if (eq.type != Token.Type.ASSIGN) {
|
||||
throw ScriptError(eq.pos, "type alias $name expects '='")
|
||||
throw ScriptError(eq.pos, "type alias $qualifiedName expects '='")
|
||||
}
|
||||
parseTypeExpressionWithMini().first
|
||||
} finally {
|
||||
if (typeParamNames.isNotEmpty()) pendingTypeParamStack.removeLast()
|
||||
}
|
||||
val alias = TypeAliasDecl(name, typeParams, body, nameToken.pos)
|
||||
typeAliases[name] = alias
|
||||
declareLocalName(name, isMutable = false)
|
||||
resolutionSink?.declareSymbol(name, SymbolKind.LOCAL, isMutable = false, pos = nameToken.pos)
|
||||
val alias = TypeAliasDecl(qualifiedName, typeParams, body, nameToken.pos)
|
||||
typeAliases[qualifiedName] = alias
|
||||
declareLocalName(declaredName, isMutable = false)
|
||||
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
|
||||
|
||||
val aliasExpr = net.sergeych.lyng.obj.TypeDeclRef(body, nameToken.pos)
|
||||
val initStmt = ExpressionStatement(aliasExpr, nameToken.pos)
|
||||
val slotPlan = slotPlanStack.lastOrNull()
|
||||
val slotIndex = slotPlan?.slots?.get(name)?.index
|
||||
val slotIndex = slotPlan?.slots?.get(declaredName)?.index
|
||||
val scopeId = slotPlan?.id
|
||||
return VarDeclStatement(
|
||||
name = name,
|
||||
name = declaredName,
|
||||
isMutable = false,
|
||||
visibility = Visibility.Public,
|
||||
initializer = initStmt,
|
||||
@ -749,6 +848,10 @@ class Compiler(
|
||||
val ids = resolveImplicitThisMemberIds(name, pos, 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 moduleEntry = modulePlan?.slots?.get(name)
|
||||
if (moduleEntry != null) {
|
||||
@ -1634,6 +1737,7 @@ class Compiler(
|
||||
}
|
||||
is QualifiedThisMethodSlotCallRef -> true
|
||||
is QualifiedThisFieldSlotRef -> true
|
||||
is ClassScopeMemberRef -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -2015,56 +2119,65 @@ class Compiler(
|
||||
when (nt.type) {
|
||||
Token.Type.LPAREN -> {
|
||||
cc.next()
|
||||
// instance method call
|
||||
val receiverType = if (next.value == "apply" || next.value == "run") {
|
||||
inferReceiverTypeFromRef(left)
|
||||
} else null
|
||||
val parsed = parseArgs(receiverType)
|
||||
val args = parsed.first
|
||||
val tailBlock = parsed.second
|
||||
if (left is LocalVarRef && left.name == "scope") {
|
||||
val first = args.firstOrNull()?.value
|
||||
val const = (first as? ExpressionStatement)?.ref as? ConstRef
|
||||
val name = const?.constValue as? ObjString
|
||||
if (name != null) {
|
||||
resolutionSink?.referenceReflection(name.value, next.pos)
|
||||
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
|
||||
val receiverType = if (next.value == "apply" || next.value == "run") {
|
||||
inferReceiverTypeFromRef(left)
|
||||
} else null
|
||||
val parsed = parseArgs(receiverType)
|
||||
val args = parsed.first
|
||||
val tailBlock = parsed.second
|
||||
if (left is LocalVarRef && left.name == "scope") {
|
||||
val first = args.firstOrNull()?.value
|
||||
val const = (first as? ExpressionStatement)?.ref as? ConstRef
|
||||
val name = const?.constValue as? ObjString
|
||||
if (name != null) {
|
||||
resolutionSink?.referenceReflection(name.value, next.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
isCall = true
|
||||
operand = when (left) {
|
||||
is LocalVarRef -> if (left.name == "this") {
|
||||
resolutionSink?.referenceMember(next.value, next.pos)
|
||||
val implicitType = currentImplicitThisTypeName()
|
||||
val ids = resolveMemberIds(next.value, next.pos, implicitType)
|
||||
ThisMethodSlotCallRef(next.value, ids.methodId, args, tailBlock, isOptional)
|
||||
} else if (left.name == "scope") {
|
||||
if (next.value == "get" || next.value == "set") {
|
||||
val first = args.firstOrNull()?.value
|
||||
val const = (first as? ExpressionStatement)?.ref as? ConstRef
|
||||
val name = const?.constValue as? ObjString
|
||||
if (name != null) {
|
||||
resolutionSink?.referenceReflection(name.value, next.pos)
|
||||
isCall = true
|
||||
operand = when (left) {
|
||||
is LocalVarRef -> if (left.name == "this") {
|
||||
resolutionSink?.referenceMember(next.value, next.pos)
|
||||
val implicitType = currentImplicitThisTypeName()
|
||||
val ids = resolveMemberIds(next.value, next.pos, implicitType)
|
||||
ThisMethodSlotCallRef(next.value, ids.methodId, args, tailBlock, isOptional)
|
||||
} else if (left.name == "scope") {
|
||||
if (next.value == "get" || next.value == "set") {
|
||||
val first = args.firstOrNull()?.value
|
||||
val const = (first as? ExpressionStatement)?.ref as? ConstRef
|
||||
val name = const?.constValue as? ObjString
|
||||
if (name != null) {
|
||||
resolutionSink?.referenceReflection(name.value, next.pos)
|
||||
}
|
||||
}
|
||||
MethodCallRef(left, next.value, args, tailBlock, isOptional)
|
||||
} else {
|
||||
enforceReceiverTypeForMember(left, next.value, next.pos)
|
||||
MethodCallRef(left, next.value, args, tailBlock, isOptional)
|
||||
}
|
||||
MethodCallRef(left, next.value, args, tailBlock, isOptional)
|
||||
} else {
|
||||
enforceReceiverTypeForMember(left, next.value, next.pos)
|
||||
MethodCallRef(left, next.value, args, tailBlock, isOptional)
|
||||
}
|
||||
is QualifiedThisRef ->
|
||||
QualifiedThisMethodSlotCallRef(
|
||||
left.typeName,
|
||||
next.value,
|
||||
resolveMemberIds(next.value, next.pos, left.typeName).methodId,
|
||||
args,
|
||||
tailBlock,
|
||||
isOptional
|
||||
).also {
|
||||
resolutionSink?.referenceMember(next.value, next.pos, left.typeName)
|
||||
is QualifiedThisRef ->
|
||||
QualifiedThisMethodSlotCallRef(
|
||||
left.typeName,
|
||||
next.value,
|
||||
resolveMemberIds(next.value, next.pos, left.typeName).methodId,
|
||||
args,
|
||||
tailBlock,
|
||||
isOptional
|
||||
).also {
|
||||
resolutionSink?.referenceMember(next.value, next.pos, left.typeName)
|
||||
}
|
||||
else -> {
|
||||
enforceReceiverTypeForMember(left, next.value, next.pos)
|
||||
MethodCallRef(left, next.value, args, tailBlock, isOptional)
|
||||
}
|
||||
else -> {
|
||||
enforceReceiverTypeForMember(left, next.value, next.pos)
|
||||
MethodCallRef(left, next.value, args, tailBlock, isOptional)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2872,7 +2985,15 @@ class Compiler(
|
||||
pos: Pos,
|
||||
seen: MutableSet<String>
|
||||
): 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}")
|
||||
val bindings = buildTypeAliasBindings(alias, args, pos)
|
||||
val substituted = substituteTypeAliasTypeVars(alias.body, bindings)
|
||||
@ -3534,6 +3655,10 @@ class Compiler(
|
||||
?: nameObjClass[ref.name]
|
||||
?: resolveClassByName(ref.name)
|
||||
}
|
||||
is ClassScopeMemberRef -> {
|
||||
val targetClass = resolveClassByName(ref.ownerClassName()) ?: return null
|
||||
inferFieldReturnClass(targetClass, ref.name)
|
||||
}
|
||||
is ListLiteralRef -> ObjList.type
|
||||
is MapLiteralRef -> ObjMap.type
|
||||
is RangeRef -> ObjRange.type
|
||||
@ -3568,6 +3693,10 @@ class Compiler(
|
||||
is LocalVarRef -> nameObjClass[ref.name]
|
||||
?: nameTypeDecl[ref.name]?.let { resolveTypeDeclObjClass(it) }
|
||||
?: resolveClassByName(ref.name)
|
||||
is ClassScopeMemberRef -> {
|
||||
val targetClass = resolveClassByName(ref.ownerClassName())
|
||||
inferFieldReturnClass(targetClass, ref.name)
|
||||
}
|
||||
is ImplicitThisMemberRef -> {
|
||||
val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName()
|
||||
val targetClass = typeName?.let { resolveClassByName(it) }
|
||||
@ -3632,6 +3761,11 @@ class Compiler(
|
||||
is ObjString -> ObjString.type
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -3828,6 +3962,11 @@ class Compiler(
|
||||
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) {
|
||||
if (left is LocalVarRef && left.name == "scope") return
|
||||
if (left is LocalSlotRef && left.name == "scope") return
|
||||
@ -5098,7 +5237,22 @@ class Compiler(
|
||||
val doc = pendingDeclDoc ?: consumePendingDoc()
|
||||
pendingDeclDoc = 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:
|
||||
val names = mutableListOf<String>()
|
||||
val positions = mutableListOf<Pos>()
|
||||
@ -5134,7 +5288,7 @@ class Compiler(
|
||||
miniSink?.onEnumDecl(
|
||||
MiniEnumDecl(
|
||||
range = MiniRange(startPos, cc.currentPos()),
|
||||
name = nameToken.value,
|
||||
name = declaredName,
|
||||
entries = names,
|
||||
doc = doc,
|
||||
nameStart = nameToken.pos,
|
||||
@ -5142,28 +5296,69 @@ class Compiler(
|
||||
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)
|
||||
fieldIds["entries"] = 0
|
||||
for ((index, entry) in names.withIndex()) {
|
||||
fieldIds[entry] = index + 1
|
||||
}
|
||||
val methodIds = mapOf("valueOf" to 0)
|
||||
compileClassInfos[nameToken.value] = CompileClassInfo(
|
||||
name = nameToken.value,
|
||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||
name = qualifiedName,
|
||||
fieldIds = fieldIds,
|
||||
methodIds = methodIds,
|
||||
nextFieldId = fieldIds.size,
|
||||
nextMethodId = methodIds.size,
|
||||
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 enumDeclStatement = object : Statement() {
|
||||
override val pos: Pos = stmtPos
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
val enumClass = ObjEnumClass.createSimpleEnum(nameToken.value, names)
|
||||
scope.addItem(nameToken.value, false, enumClass, recordType = ObjRecord.Type.Enum)
|
||||
val enumClass = ObjEnumClass.createSimpleEnum(qualifiedName, names)
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -5175,10 +5370,18 @@ class Compiler(
|
||||
val nameToken = if (next.type == Token.Type.ID) cc.requireToken(Token.Type.ID) else null
|
||||
|
||||
val startPos = pendingDeclStart ?: nameToken?.pos ?: cc.current().pos
|
||||
val className = nameToken?.value ?: generateAnonName(startPos)
|
||||
if (nameToken != null) {
|
||||
resolutionSink?.declareSymbol(nameToken.value, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
|
||||
declareLocalName(nameToken.value, isMutable = false)
|
||||
val declaredName = nameToken?.value
|
||||
val outerClassName = currentEnclosingClassName()
|
||||
val baseName = declaredName ?: generateAnonName(startPos)
|
||||
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()
|
||||
@ -5215,7 +5418,7 @@ class Compiler(
|
||||
run {
|
||||
val node = MiniClassDecl(
|
||||
range = MiniRange(startPos, cc.currentPos()),
|
||||
name = className,
|
||||
name = declaredName ?: className,
|
||||
bases = baseSpecs.map { it.name },
|
||||
bodyRange = null,
|
||||
doc = doc,
|
||||
@ -5232,6 +5435,8 @@ class Compiler(
|
||||
resolutionSink?.enterScope(ScopeKind.CLASS, startPos, className, baseSpecs.map { it.name })
|
||||
val classCtx = codeContexts.lastOrNull() as? CodeContext.ClassBody
|
||||
classCtx?.let { ctx ->
|
||||
val callableMembers = classScopeCallableMembersByClassName.getOrPut(className) { mutableSetOf() }
|
||||
predeclareClassScopeMembers(className, ctx.classScopeMembers, callableMembers)
|
||||
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
|
||||
ctx.memberFieldIds.putAll(baseIds.fieldIds)
|
||||
ctx.memberMethodIds.putAll(baseIds.methodIds)
|
||||
@ -5254,6 +5459,9 @@ class Compiler(
|
||||
)
|
||||
}
|
||||
}
|
||||
if (declaredName != null) {
|
||||
registerClassScopeFieldType(outerClassName, declaredName, className)
|
||||
}
|
||||
parsed
|
||||
} finally {
|
||||
slotPlanStack.removeLast()
|
||||
@ -5269,7 +5477,7 @@ class Compiler(
|
||||
run {
|
||||
val node = MiniClassDecl(
|
||||
range = MiniRange(startPos, cc.currentPos()),
|
||||
name = className,
|
||||
name = declaredName ?: className,
|
||||
bases = baseSpecs.map { it.name },
|
||||
bodyRange = null,
|
||||
doc = doc,
|
||||
@ -5291,6 +5499,9 @@ class Compiler(
|
||||
baseNames = baseSpecs.map { it.name }
|
||||
)
|
||||
}
|
||||
if (declaredName != null) {
|
||||
registerClassScopeFieldType(outerClassName, declaredName, className)
|
||||
}
|
||||
cc.restorePos(saved)
|
||||
null
|
||||
}
|
||||
@ -5324,8 +5535,8 @@ class Compiler(
|
||||
|
||||
// Create instance (singleton)
|
||||
val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY))
|
||||
if (nameToken != null)
|
||||
scope.addItem(className, false, instance)
|
||||
if (declaredName != null)
|
||||
scope.addItem(declaredName, false, instance)
|
||||
return instance
|
||||
}
|
||||
}
|
||||
@ -5338,8 +5549,17 @@ class Compiler(
|
||||
val doc = pendingDeclDoc ?: consumePendingDoc()
|
||||
pendingDeclDoc = null
|
||||
pendingDeclStart = null
|
||||
resolutionSink?.declareSymbol(nameToken.value, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
|
||||
return inCodeContext(CodeContext.ClassBody(nameToken.value, isExtern = isExtern)) {
|
||||
val declaredName = nameToken.value
|
||||
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 typeParamDecls = parseTypeParamList()
|
||||
classCtx?.typeParamDecls = typeParamDecls
|
||||
@ -5366,7 +5586,7 @@ class Compiler(
|
||||
if (param.accessType != null) {
|
||||
val declClass = resolveTypeDeclObjClass(param.type)
|
||||
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 {
|
||||
val node = MiniClassDecl(
|
||||
range = MiniRange(startPos, cc.currentPos()),
|
||||
name = nameToken.value,
|
||||
name = declaredName,
|
||||
bases = baseSpecs.map { it.name },
|
||||
bodyRange = null,
|
||||
ctorFields = ctorFields,
|
||||
@ -5453,8 +5673,8 @@ class Compiler(
|
||||
// parse body
|
||||
val bodyStart = next.pos
|
||||
slotPlanStack.add(classSlotPlan)
|
||||
resolutionSink?.declareClass(nameToken.value, baseSpecs.map { it.name }, startPos)
|
||||
resolutionSink?.enterScope(ScopeKind.CLASS, startPos, nameToken.value, baseSpecs.map { it.name })
|
||||
resolutionSink?.declareClass(qualifiedName, baseSpecs.map { it.name }, startPos)
|
||||
resolutionSink?.enterScope(ScopeKind.CLASS, startPos, qualifiedName, baseSpecs.map { it.name })
|
||||
constructorArgsDeclaration?.params?.forEach { param ->
|
||||
val accessType = param.accessType
|
||||
val kind = if (accessType != null) SymbolKind.MEMBER else SymbolKind.PARAM
|
||||
@ -5463,8 +5683,10 @@ class Compiler(
|
||||
}
|
||||
val st = try {
|
||||
classCtx?.let { ctx ->
|
||||
val callableMembers = classScopeCallableMembersByClassName.getOrPut(qualifiedName) { mutableSetOf() }
|
||||
predeclareClassScopeMembers(qualifiedName, ctx.classScopeMembers, callableMembers)
|
||||
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) {
|
||||
ctx.memberFieldIds.putAll(existingExternInfo.fieldIds)
|
||||
ctx.memberMethodIds.putAll(existingExternInfo.methodIds)
|
||||
@ -5474,18 +5696,18 @@ class Compiler(
|
||||
val hasField = member in existingExternInfo.fieldIds
|
||||
val hasMethod = member in existingExternInfo.methodIds
|
||||
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 ->
|
||||
if (param.accessType == null) return@forEach
|
||||
if (!ctx.memberFieldIds.containsKey(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
|
||||
}
|
||||
}
|
||||
compileClassInfos[nameToken.value] = existingExternInfo
|
||||
compileClassInfos[qualifiedName] = existingExternInfo
|
||||
} else {
|
||||
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
|
||||
ctx.memberFieldIds.putAll(baseIds.fieldIds)
|
||||
@ -5512,8 +5734,8 @@ class Compiler(
|
||||
ctx.memberFieldIds[param.name] = ctx.nextFieldId++
|
||||
}
|
||||
}
|
||||
compileClassInfos[nameToken.value] = CompileClassInfo(
|
||||
name = nameToken.value,
|
||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||
name = qualifiedName,
|
||||
fieldIds = ctx.memberFieldIds.toMap(),
|
||||
methodIds = ctx.memberMethodIds.toMap(),
|
||||
nextFieldId = ctx.nextFieldId,
|
||||
@ -5527,8 +5749,8 @@ class Compiler(
|
||||
}
|
||||
if (!isExtern) {
|
||||
classCtx?.let { ctx ->
|
||||
compileClassInfos[nameToken.value] = CompileClassInfo(
|
||||
name = nameToken.value,
|
||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||
name = qualifiedName,
|
||||
fieldIds = ctx.memberFieldIds.toMap(),
|
||||
methodIds = ctx.memberMethodIds.toMap(),
|
||||
nextFieldId = ctx.nextFieldId,
|
||||
@ -5537,6 +5759,7 @@ class Compiler(
|
||||
)
|
||||
}
|
||||
}
|
||||
registerClassScopeFieldType(outerClassName, declaredName, qualifiedName)
|
||||
parsed
|
||||
} finally {
|
||||
slotPlanStack.removeLast()
|
||||
@ -5552,7 +5775,7 @@ class Compiler(
|
||||
run {
|
||||
val node = MiniClassDecl(
|
||||
range = MiniRange(startPos, cc.currentPos()),
|
||||
name = nameToken.value,
|
||||
name = declaredName,
|
||||
bases = baseSpecs.map { it.name },
|
||||
bodyRange = null,
|
||||
ctorFields = ctorFields,
|
||||
@ -5562,9 +5785,9 @@ class Compiler(
|
||||
)
|
||||
miniSink?.onClassDecl(node)
|
||||
}
|
||||
resolutionSink?.declareClass(nameToken.value, baseSpecs.map { it.name }, startPos)
|
||||
resolutionSink?.declareClass(qualifiedName, baseSpecs.map { it.name }, startPos)
|
||||
classCtx?.let { ctx ->
|
||||
val existingExternInfo = if (isExtern) resolveCompileClassInfo(nameToken.value) else null
|
||||
val existingExternInfo = if (isExtern) resolveCompileClassInfo(qualifiedName) else null
|
||||
if (existingExternInfo != null) {
|
||||
ctx.memberFieldIds.putAll(existingExternInfo.fieldIds)
|
||||
ctx.memberMethodIds.putAll(existingExternInfo.methodIds)
|
||||
@ -5574,18 +5797,18 @@ class Compiler(
|
||||
val hasField = member in existingExternInfo.fieldIds
|
||||
val hasMethod = member in existingExternInfo.methodIds
|
||||
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 ->
|
||||
if (param.accessType == null) return@forEach
|
||||
if (!ctx.memberFieldIds.containsKey(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
|
||||
}
|
||||
}
|
||||
compileClassInfos[nameToken.value] = existingExternInfo
|
||||
compileClassInfos[qualifiedName] = existingExternInfo
|
||||
} else {
|
||||
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
|
||||
ctx.memberFieldIds.putAll(baseIds.fieldIds)
|
||||
@ -5612,8 +5835,8 @@ class Compiler(
|
||||
ctx.memberFieldIds[param.name] = ctx.nextFieldId++
|
||||
}
|
||||
}
|
||||
compileClassInfos[nameToken.value] = CompileClassInfo(
|
||||
name = nameToken.value,
|
||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||
name = qualifiedName,
|
||||
fieldIds = ctx.memberFieldIds.toMap(),
|
||||
methodIds = ctx.memberMethodIds.toMap(),
|
||||
nextFieldId = ctx.nextFieldId,
|
||||
@ -5622,6 +5845,7 @@ class Compiler(
|
||||
)
|
||||
}
|
||||
}
|
||||
registerClassScopeFieldType(outerClassName, declaredName, qualifiedName)
|
||||
// restore if no body starts here
|
||||
cc.restorePos(saved)
|
||||
null
|
||||
@ -5631,7 +5855,7 @@ class Compiler(
|
||||
val initScope = popInitScope()
|
||||
|
||||
// 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 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
|
||||
val classScope = scope.createChildScope(newThisObj = newClass)
|
||||
// Set lexical class context for visibility tagging inside class body
|
||||
@ -5715,7 +5939,7 @@ class Compiler(
|
||||
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 ThisFieldSlotRef && 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) {
|
||||
is ObjClass -> value
|
||||
is ObjString -> ObjString.type
|
||||
@ -6774,6 +7003,12 @@ class Compiler(
|
||||
else -> return null
|
||||
}
|
||||
val name = rawName.substringAfterLast('.')
|
||||
if (!rawName.contains('.')) {
|
||||
val classCtx = currentEnclosingClassName()
|
||||
if (classCtx != null) {
|
||||
resolveClassByName("$classCtx.$rawName")?.let { return it }
|
||||
}
|
||||
}
|
||||
return when (name) {
|
||||
"Object", "Obj" -> Obj.rootObjectType
|
||||
"String" -> ObjString.type
|
||||
|
||||
@ -242,6 +242,7 @@ private fun applyEnumConstantHeuristics(
|
||||
var j = i + 1
|
||||
// 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.STAR) j++
|
||||
if (j < tokens.size && tokens[j].type == Type.LBRACE) {
|
||||
j++
|
||||
while (j < tokens.size) {
|
||||
|
||||
@ -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,
|
||||
* then falls back to member lookup on `this`.
|
||||
|
||||
@ -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
|
||||
fun enumSerializationTest() = runTest {
|
||||
eval(
|
||||
|
||||
@ -75,6 +75,18 @@ class B { fun foo() = 2 }
|
||||
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 policy is configurable:
|
||||
- Locals may shadow parameters (allowed by default).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user