Refactored ObjRef evaluation logic for enhanced performance and consistency across unary, binary, and field operations. Added evalValue overrides for more streamlined value resolution.
This commit is contained in:
1 parent
827df9c8cd
commit
6b957ae6a3
28 files changed
+1339
-506
No files matched your search
@@ -169,9 +169,9 @@ class Compiler(
|
||||
miniSink?.onScriptStart(start)
|
||||
do {
|
||||
val t = cc.current()
|
||||
if (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINLGE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) {
|
||||
if (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) {
|
||||
when (t.type) {
|
||||
Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> pushPendingDocToken(t)
|
||||
Token.Type.SINGLE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> pushPendingDocToken(t)
|
||||
Token.Type.NEWLINE -> {
|
||||
// A standalone newline not immediately following a comment resets doc buffer
|
||||
if (!prevWasComment) clearPendingDoc() else prevWasComment = false
|
||||
@@ -316,7 +316,7 @@ class Compiler(
|
||||
}
|
||||
|
||||
Token.Type.LABEL -> continue
|
||||
Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> continue
|
||||
Token.Type.SINGLE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> continue
|
||||
|
||||
Token.Type.NEWLINE -> continue
|
||||
|
||||
@@ -407,7 +407,7 @@ class Compiler(
|
||||
val t = cc.next()
|
||||
val startPos = t.pos
|
||||
when (t.type) {
|
||||
// Token.Type.NEWLINE, Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT-> {
|
||||
// Token.Type.NEWLINE, Token.Type.SINGLE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT-> {
|
||||
// continue
|
||||
// }
|
||||
|
||||
@@ -606,7 +606,7 @@ class Compiler(
|
||||
// to skip in parseExpression:
|
||||
val current = cc.current()
|
||||
val right =
|
||||
if (current.type == Token.Type.NEWLINE || current.type == Token.Type.SINLGE_LINE_COMMENT)
|
||||
if (current.type == Token.Type.NEWLINE || current.type == Token.Type.SINGLE_LINE_COMMENT)
|
||||
null
|
||||
else
|
||||
parseExpression()
|
||||
@@ -887,7 +887,7 @@ class Compiler(
|
||||
}
|
||||
|
||||
Token.Type.NEWLINE -> {}
|
||||
Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT -> {}
|
||||
Token.Type.MULTILINE_COMMENT, Token.Type.SINGLE_LINE_COMMENT -> {}
|
||||
|
||||
Token.Type.ID -> {
|
||||
// visibility
|
||||
@@ -2734,7 +2734,8 @@ class Compiler(
|
||||
doc = declDocLocal,
|
||||
nameStart = nameStartPos,
|
||||
receiver = receiverMini,
|
||||
isExtern = actualExtern
|
||||
isExtern = actualExtern,
|
||||
isStatic = isStatic
|
||||
)
|
||||
miniSink?.onFunDecl(node)
|
||||
pendingDeclDoc = null
|
||||
@@ -2941,10 +2942,11 @@ class Compiler(
|
||||
doc = declDocLocal,
|
||||
nameStart = nameStartPos,
|
||||
receiver = receiverMini,
|
||||
isExtern = actualExtern
|
||||
isExtern = actualExtern,
|
||||
isStatic = isStatic
|
||||
)
|
||||
miniSink?.onFunDecl(node)
|
||||
miniSink?.onExitFunction(cc.currentPos())
|
||||
miniSink?.onFunDecl(node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3002,7 +3004,8 @@ class Compiler(
|
||||
initRange = null,
|
||||
doc = pendingDeclDoc,
|
||||
nameStart = namePos,
|
||||
isExtern = actualExtern
|
||||
isExtern = actualExtern,
|
||||
isStatic = false
|
||||
)
|
||||
miniSink?.onValDecl(node)
|
||||
}
|
||||
@@ -3194,7 +3197,8 @@ class Compiler(
|
||||
doc = pendingDeclDoc,
|
||||
nameStart = nameStartPos,
|
||||
receiver = receiverMini,
|
||||
isExtern = actualExtern
|
||||
isExtern = actualExtern,
|
||||
isStatic = isStatic
|
||||
)
|
||||
miniSink?.onValDecl(node)
|
||||
pendingDeclDoc = null
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -238,7 +238,7 @@ class CompilerContext(val tokens: List<Token>) {
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT, Token.Type.NEWLINE -> {}
|
||||
Token.Type.MULTILINE_COMMENT, Token.Type.SINGLE_LINE_COMMENT, Token.Type.NEWLINE -> {}
|
||||
else -> {
|
||||
restorePos(pos); return false
|
||||
}
|
||||
@@ -265,6 +265,6 @@ class CompilerContext(val tokens: List<Token>) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val wstokens = setOf(Token.Type.NEWLINE, Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT)
|
||||
val wstokens = setOf(Token.Type.NEWLINE, Token.Type.MULTILINE_COMMENT, Token.Type.SINGLE_LINE_COMMENT)
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,7 @@ private class Parser(fromPos: Pos) {
|
||||
pos.advance()
|
||||
val body = loadToEndOfLine()
|
||||
// Include the leading '//' and do not trim; keep exact lexeme (excluding preceding codepoint)
|
||||
Token("//" + body, from, Token.Type.SINLGE_LINE_COMMENT)
|
||||
Token("//" + body, from, Token.Type.SINGLE_LINE_COMMENT)
|
||||
}
|
||||
|
||||
'*' -> {
|
||||
|
||||
@@ -22,7 +22,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
throw ScriptError(pos, text)
|
||||
}
|
||||
|
||||
val isComment: Boolean by lazy { type == Type.SINLGE_LINE_COMMENT || type == Type.MULTILINE_COMMENT }
|
||||
val isComment: Boolean by lazy { type == Type.SINGLE_LINE_COMMENT || type == Type.MULTILINE_COMMENT }
|
||||
|
||||
fun isId(text: String) =
|
||||
type == Type.ID && value == text
|
||||
@@ -41,7 +41,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
SHUTTLE,
|
||||
AND, BITAND, OR, BITOR, BITXOR, NOT, BITNOT, DOT, ARROW, EQARROW, QUESTION, COLONCOLON,
|
||||
SHL, SHR,
|
||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||
SINGLE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||
LABEL, ATLABEL, // label@ at@label
|
||||
// type-checking/casting
|
||||
AS, ASNULL, OBJECT,
|
||||
|
||||
@@ -67,7 +67,7 @@ private fun kindOf(type: Type, value: String): HighlightKind? = when (type) {
|
||||
Type.REGEX -> HighlightKind.Regex
|
||||
|
||||
// comments
|
||||
Type.SINLGE_LINE_COMMENT, Type.MULTILINE_COMMENT -> HighlightKind.Comment
|
||||
Type.SINGLE_LINE_COMMENT, Type.MULTILINE_COMMENT -> HighlightKind.Comment
|
||||
|
||||
// punctuation
|
||||
Type.LPAREN, Type.RPAREN, Type.LBRACE, Type.RBRACE, Type.LBRACKET, Type.RBRACKET,
|
||||
|
||||
@@ -148,6 +148,8 @@ class ModuleDocsBuilder internal constructor(private val moduleName: String) {
|
||||
body = null,
|
||||
doc = md,
|
||||
nameStart = Pos.builtIn,
|
||||
isExtern = false,
|
||||
isStatic = false
|
||||
)
|
||||
}
|
||||
|
||||
@@ -167,6 +169,8 @@ class ModuleDocsBuilder internal constructor(private val moduleName: String) {
|
||||
initRange = null,
|
||||
doc = md,
|
||||
nameStart = Pos.builtIn,
|
||||
isExtern = false,
|
||||
isStatic = false
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ data class CompletionItem(
|
||||
val kind: Kind,
|
||||
val tailText: String? = null,
|
||||
val typeText: String? = null,
|
||||
val priority: Double = 0.0,
|
||||
)
|
||||
|
||||
enum class Kind { Function, Class_, Enum, Value, Method, Field }
|
||||
@@ -107,7 +108,7 @@ object CompletionEngineLight {
|
||||
val locals = DocLookupUtils.extractLocalsAt(text, caret)
|
||||
for (name in locals) {
|
||||
if (name.startsWith(prefix, true)) {
|
||||
out.add(CompletionItem(name, Kind.Value))
|
||||
out.add(CompletionItem(name, Kind.Value, priority = 150.0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,17 +169,17 @@ object CompletionEngineLight {
|
||||
when (node) {
|
||||
is MiniFunDecl -> {
|
||||
for (p in node.params) {
|
||||
add(CompletionItem(p.name, Kind.Value, typeText = typeOf(p.type)))
|
||||
add(CompletionItem(p.name, Kind.Value, typeText = typeOf(p.type), priority = 200.0))
|
||||
}
|
||||
}
|
||||
is MiniClassDecl -> {
|
||||
// Propose constructor parameters (ctorFields)
|
||||
for (p in node.ctorFields) {
|
||||
add(CompletionItem(p.name, if (p.mutable) Kind.Value else Kind.Field, typeText = typeOf(p.type)))
|
||||
add(CompletionItem(p.name, if (p.mutable) Kind.Value else Kind.Field, typeText = typeOf(p.type), priority = 200.0))
|
||||
}
|
||||
// Propose class-level fields
|
||||
for (p in node.classFields) {
|
||||
add(CompletionItem(p.name, if (p.mutable) Kind.Value else Kind.Field, typeText = typeOf(p.type)))
|
||||
add(CompletionItem(p.name, if (p.mutable) Kind.Value else Kind.Field, typeText = typeOf(p.type), priority = 100.0))
|
||||
}
|
||||
// Process members (methods/fields)
|
||||
for (m in node.members) {
|
||||
@@ -189,10 +190,10 @@ object CompletionEngineLight {
|
||||
when (m) {
|
||||
is MiniMemberFunDecl -> {
|
||||
val params = m.params.joinToString(", ") { it.name }
|
||||
add(CompletionItem(m.name, Kind.Method, tailText = "(${params})", typeText = typeOf(m.returnType)))
|
||||
add(CompletionItem(m.name, Kind.Method, tailText = "(${params})", typeText = typeOf(m.returnType), priority = 100.0))
|
||||
}
|
||||
is MiniMemberValDecl -> {
|
||||
add(CompletionItem(m.name, if (m.mutable) Kind.Value else Kind.Field, typeText = typeOf(m.type)))
|
||||
add(CompletionItem(m.name, if (m.mutable) Kind.Value else Kind.Field, typeText = typeOf(m.type), priority = 100.0))
|
||||
}
|
||||
is MiniInitDecl -> {}
|
||||
}
|
||||
@@ -200,7 +201,7 @@ object CompletionEngineLight {
|
||||
}
|
||||
is MiniMemberFunDecl -> {
|
||||
for (p in node.params) {
|
||||
add(CompletionItem(p.name, Kind.Value, typeText = typeOf(p.type)))
|
||||
add(CompletionItem(p.name, Kind.Value, typeText = typeOf(p.type), priority = 200.0))
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
@@ -250,7 +251,7 @@ object CompletionEngineLight {
|
||||
"Array" -> listOf("Collection", "Iterable").forEach { if (visited.add(it)) addMembersOf(it, false) }
|
||||
}
|
||||
|
||||
fun emitGroup(map: LinkedHashMap<String, MutableList<MiniMemberDecl>>) {
|
||||
fun emitGroup(map: LinkedHashMap<String, MutableList<MiniMemberDecl>>, groupPriority: Double) {
|
||||
for (name in map.keys.sortedBy { it.lowercase() }) {
|
||||
val variants = map[name] ?: continue
|
||||
// Prefer a method with a known return type; else any method; else first variant
|
||||
@@ -265,7 +266,7 @@ object CompletionEngineLight {
|
||||
val params = rep.params.joinToString(", ") { it.name }
|
||||
val extra = variants.count { it is MiniMemberFunDecl } - 1
|
||||
val ov = if (extra > 0) " (+$extra overloads)" else ""
|
||||
val ci = CompletionItem(name, Kind.Method, tailText = "(${params})$ov", typeText = typeOf(rep.returnType))
|
||||
val ci = CompletionItem(name, Kind.Method, tailText = "(${params})$ov", typeText = typeOf(rep.returnType), priority = groupPriority)
|
||||
if (ci.name.startsWith(prefix, true)) out += ci
|
||||
}
|
||||
is MiniMemberValDecl -> {
|
||||
@@ -273,7 +274,7 @@ object CompletionEngineLight {
|
||||
val chosen = variants.asSequence()
|
||||
.filterIsInstance<MiniMemberValDecl>()
|
||||
.firstOrNull { it.type != null } ?: rep
|
||||
val ci = CompletionItem(name, Kind.Field, typeText = typeOf(chosen.type))
|
||||
val ci = CompletionItem(name, Kind.Field, typeText = typeOf(chosen.type), priority = groupPriority)
|
||||
if (ci.name.startsWith(prefix, true)) out += ci
|
||||
}
|
||||
is MiniInitDecl -> {}
|
||||
@@ -281,8 +282,8 @@ object CompletionEngineLight {
|
||||
}
|
||||
}
|
||||
|
||||
emitGroup(directMap)
|
||||
emitGroup(inheritedMap)
|
||||
emitGroup(directMap, 100.0)
|
||||
emitGroup(inheritedMap, 0.0)
|
||||
|
||||
// Supplement with extension members (both stdlib and local)
|
||||
run {
|
||||
@@ -296,22 +297,22 @@ object CompletionEngineLight {
|
||||
val ci = when (m) {
|
||||
is MiniMemberFunDecl -> {
|
||||
val params = m.params.joinToString(", ") { it.name }
|
||||
CompletionItem(name, Kind.Method, tailText = "(${params})", typeText = typeOf(m.returnType))
|
||||
CompletionItem(name, Kind.Method, tailText = "(${params})", typeText = typeOf(m.returnType), priority = 50.0)
|
||||
}
|
||||
is MiniFunDecl -> {
|
||||
val params = m.params.joinToString(", ") { it.name }
|
||||
CompletionItem(name, Kind.Method, tailText = "(${params})", typeText = typeOf(m.returnType))
|
||||
CompletionItem(name, Kind.Method, tailText = "(${params})", typeText = typeOf(m.returnType), priority = 50.0)
|
||||
}
|
||||
is MiniMemberValDecl -> CompletionItem(name, Kind.Field, typeText = typeOf(m.type))
|
||||
is MiniValDecl -> CompletionItem(name, Kind.Field, typeText = typeOf(m.type))
|
||||
else -> CompletionItem(name, Kind.Method, tailText = "()", typeText = null)
|
||||
is MiniMemberValDecl -> CompletionItem(name, Kind.Field, typeText = typeOf(m.type), priority = 50.0)
|
||||
is MiniValDecl -> CompletionItem(name, Kind.Field, typeText = typeOf(m.type), priority = 50.0)
|
||||
else -> CompletionItem(name, Kind.Method, tailText = "()", typeText = null, priority = 50.0)
|
||||
}
|
||||
if (ci.name.startsWith(prefix, true)) {
|
||||
out += ci
|
||||
already.add(name)
|
||||
}
|
||||
} else {
|
||||
val ci = CompletionItem(name, Kind.Method, tailText = "()", typeText = null)
|
||||
val ci = CompletionItem(name, Kind.Method, tailText = "()", typeText = null, priority = 50.0)
|
||||
if (ci.name.startsWith(prefix, true)) {
|
||||
out += ci
|
||||
already.add(name)
|
||||
|
||||
@@ -87,6 +87,7 @@ sealed interface MiniNamedDecl : MiniNode {
|
||||
// Start position of the declaration name identifier in source; end can be derived as start + name.length
|
||||
val nameStart: Pos
|
||||
val isExtern: Boolean
|
||||
val isStatic: Boolean
|
||||
}
|
||||
|
||||
sealed interface MiniDecl : MiniNamedDecl
|
||||
@@ -114,7 +115,8 @@ data class MiniFunDecl(
|
||||
override val doc: MiniDoc?,
|
||||
override val nameStart: Pos,
|
||||
val receiver: MiniTypeRef? = null,
|
||||
override val isExtern: Boolean = false
|
||||
override val isExtern: Boolean = false,
|
||||
override val isStatic: Boolean = false,
|
||||
) : MiniDecl
|
||||
|
||||
data class MiniValDecl(
|
||||
@@ -126,7 +128,8 @@ data class MiniValDecl(
|
||||
override val doc: MiniDoc?,
|
||||
override val nameStart: Pos,
|
||||
val receiver: MiniTypeRef? = null,
|
||||
override val isExtern: Boolean = false
|
||||
override val isExtern: Boolean = false,
|
||||
override val isStatic: Boolean = false,
|
||||
) : MiniDecl
|
||||
|
||||
data class MiniClassDecl(
|
||||
@@ -141,6 +144,7 @@ data class MiniClassDecl(
|
||||
// Built-in extension: list of member declarations (functions and fields)
|
||||
val members: List<MiniMemberDecl> = emptyList(),
|
||||
override val isExtern: Boolean = false,
|
||||
override val isStatic: Boolean = false,
|
||||
val isObject: Boolean = false
|
||||
) : MiniDecl
|
||||
|
||||
@@ -150,7 +154,8 @@ data class MiniEnumDecl(
|
||||
val entries: List<String>,
|
||||
override val doc: MiniDoc?,
|
||||
override val nameStart: Pos,
|
||||
override val isExtern: Boolean = false
|
||||
override val isExtern: Boolean = false,
|
||||
override val isStatic: Boolean = false,
|
||||
) : MiniDecl
|
||||
|
||||
data class MiniCtorField(
|
||||
@@ -175,7 +180,7 @@ data class MiniIdentifier(
|
||||
|
||||
// --- Class member declarations (for built-in/registry docs) ---
|
||||
sealed interface MiniMemberDecl : MiniNamedDecl {
|
||||
val isStatic: Boolean
|
||||
override val isStatic: Boolean
|
||||
}
|
||||
|
||||
data class MiniMemberFunDecl(
|
||||
@@ -319,7 +324,7 @@ class MiniAstBuilder : MiniAstSink {
|
||||
returnType = attach.returnType,
|
||||
doc = attach.doc,
|
||||
nameStart = attach.nameStart,
|
||||
isStatic = false, // TODO: track static if needed
|
||||
isStatic = attach.isStatic,
|
||||
isExtern = attach.isExtern,
|
||||
body = attach.body
|
||||
)
|
||||
@@ -359,7 +364,7 @@ class MiniAstBuilder : MiniAstSink {
|
||||
initRange = attach.initRange,
|
||||
doc = attach.doc,
|
||||
nameStart = attach.nameStart,
|
||||
isStatic = false, // TODO: track static if needed
|
||||
isStatic = attach.isStatic,
|
||||
isExtern = attach.isExtern
|
||||
)
|
||||
// Duplicates for vals are rare but possible if Compiler calls it twice
|
||||
|
||||
@@ -106,6 +106,34 @@ open class ObjClass(
|
||||
val classId: Long = ClassIdGen.nextId()
|
||||
var layoutVersion: Int = 0
|
||||
|
||||
private val mangledNameCache = mutableMapOf<String, String>()
|
||||
fun mangledName(name: String): String = mangledNameCache.getOrPut(name) { "$className::$name" }
|
||||
|
||||
/**
|
||||
* Map of public member names to their effective storage keys in instanceScope.objects.
|
||||
* This is pre-calculated to avoid MRO traversal and string concatenation during common access.
|
||||
*/
|
||||
val publicMemberResolution: Map<String, String> by lazy {
|
||||
val res = mutableMapOf<String, String>()
|
||||
// Traverse MRO in REVERSED order so that child classes override parent classes in the map.
|
||||
for (cls in mro.reversed()) {
|
||||
if (cls.className == "Obj") continue
|
||||
for ((name, rec) in cls.members) {
|
||||
if (rec.visibility == Visibility.Public) {
|
||||
val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
||||
res[name] = key
|
||||
}
|
||||
}
|
||||
cls.classScope?.objects?.forEach { (name, rec) ->
|
||||
if (rec.visibility == Visibility.Public && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) {
|
||||
val key = if (rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
|
||||
res[name] = key
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
val classNameObj by lazy { ObjString(className) }
|
||||
|
||||
var constructorMeta: ArgsDeclaration? = null
|
||||
@@ -242,6 +270,43 @@ open class ObjClass(
|
||||
return instance
|
||||
}
|
||||
|
||||
/** Pre-calculated template for instanceScope.objects. */
|
||||
private val instanceObjectsTemplate: Map<String, ObjRecord> by lazy {
|
||||
val res = mutableMapOf<String, ObjRecord>()
|
||||
for (cls in mro) {
|
||||
// 1) members-defined methods and fields
|
||||
for ((k, v) in cls.members) {
|
||||
if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field)) {
|
||||
val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k
|
||||
if (!res.containsKey(key)) {
|
||||
res[key] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2) class-scope members registered during class-body execution
|
||||
cls.classScope?.objects?.forEach { (k, rec) ->
|
||||
// ONLY copy methods and delegated members from class scope to instance scope.
|
||||
// Fields in class scope are static fields and must NOT be per-instance.
|
||||
if (!rec.isAbstract && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) {
|
||||
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k
|
||||
// if not already present, copy reference for dispatch
|
||||
if (!res.containsKey(key)) {
|
||||
res[key] = rec
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
private val templateMethods: Map<String, ObjRecord> by lazy {
|
||||
instanceObjectsTemplate.filter { it.value.type == ObjRecord.Type.Fun }
|
||||
}
|
||||
|
||||
private val templateOthers: List<Pair<String, ObjRecord>> by lazy {
|
||||
instanceObjectsTemplate.filter { it.value.type != ObjRecord.Type.Fun }.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of this class and initialize its [ObjInstance.instanceScope] with
|
||||
* methods. Does NOT run initializers or constructors.
|
||||
@@ -257,28 +322,9 @@ open class ObjClass(
|
||||
// Expose instance methods (and other callable members) directly in the instance scope for fast lookup
|
||||
// This mirrors Obj.autoInstanceScope behavior for ad-hoc scopes and makes fb.method() resolution robust
|
||||
|
||||
for (cls in mro) {
|
||||
// 1) members-defined methods and fields
|
||||
for ((k, v) in cls.members) {
|
||||
if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field)) {
|
||||
val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.Delegated) "${cls.className}::$k" else k
|
||||
if (!instance.instanceScope.objects.containsKey(key)) {
|
||||
instance.instanceScope.objects[key] = if (v.type == ObjRecord.Type.Fun) v else v.copy()
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2) class-scope members registered during class-body execution
|
||||
cls.classScope?.objects?.forEach { (k, rec) ->
|
||||
// ONLY copy methods and delegated members from class scope to instance scope.
|
||||
// Fields in class scope are static fields and must NOT be per-instance.
|
||||
if (!rec.isAbstract && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) {
|
||||
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) "${cls.className}::$k" else k
|
||||
// if not already present, copy reference for dispatch
|
||||
if (!instance.instanceScope.objects.containsKey(key)) {
|
||||
instance.instanceScope.objects[key] = if (rec.type == ObjRecord.Type.Fun) rec else rec.copy()
|
||||
}
|
||||
}
|
||||
}
|
||||
instance.instanceScope.objects.putAll(templateMethods)
|
||||
for (p in templateOthers) {
|
||||
instance.instanceScope.objects[p.first] = p.second.copy()
|
||||
}
|
||||
return instance
|
||||
}
|
||||
@@ -327,7 +373,7 @@ open class ObjClass(
|
||||
for (p in meta.params) {
|
||||
val rec = instance.instanceScope.objects[p.name]
|
||||
if (rec != null) {
|
||||
val mangled = "${c.className}::${p.name}"
|
||||
val mangled = c.mangledName(p.name)
|
||||
// Always point the mangled name to the current record to keep writes consistent
|
||||
// across re-bindings
|
||||
instance.instanceScope.objects[mangled] = rec
|
||||
@@ -361,7 +407,7 @@ open class ObjClass(
|
||||
for (p in meta.params) {
|
||||
val rec = instance.instanceScope.objects[p.name]
|
||||
if (rec != null) {
|
||||
val mangled = "${c.className}::${p.name}"
|
||||
val mangled = c.mangledName(p.name)
|
||||
instance.instanceScope.objects[mangled] = rec
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,18 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||
val caller = scope.currentClassCtx
|
||||
|
||||
// Fast path for public members when outside any class context
|
||||
if (caller == null) {
|
||||
objClass.publicMemberResolution[name]?.let { key ->
|
||||
instanceScope.objects[key]?.let { rec ->
|
||||
// Directly return fields to bypass resolveRecord overhead
|
||||
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
|
||||
return rec
|
||||
return resolveRecord(scope, rec, name, rec.declaringClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0. Private mangled of current class context
|
||||
caller?.let { c ->
|
||||
// Check for private methods/properties
|
||||
@@ -43,7 +55,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
}
|
||||
}
|
||||
// Check for private fields (stored in instanceScope)
|
||||
val mangled = "${c.className}::$name"
|
||||
val mangled = c.mangledName(name)
|
||||
instanceScope.objects[mangled]?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private) {
|
||||
return resolveRecord(scope, rec, name, c)
|
||||
@@ -54,7 +66,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
// 1. MRO mangled storage
|
||||
for (cls in objClass.mro) {
|
||||
if (cls.className == "Obj") break
|
||||
val mangled = "${cls.className}::$name"
|
||||
val mangled = cls.mangledName(name)
|
||||
instanceScope.objects[mangled]?.let { rec ->
|
||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, cls, caller)) {
|
||||
return resolveRecord(scope, rec, name, cls)
|
||||
@@ -79,11 +91,11 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (obj.type.isArgument) return super.resolveRecord(scope, obj, name, decl)
|
||||
if (obj.type == ObjRecord.Type.Delegated) {
|
||||
val d = decl ?: obj.declaringClass
|
||||
val storageName = "${d?.className}::$name"
|
||||
val storageName = d?.mangledName(name) ?: name
|
||||
var del = instanceScope[storageName]?.delegate ?: obj.delegate
|
||||
if (del == null) {
|
||||
for (c in objClass.mro) {
|
||||
del = instanceScope["${c.className}::$name"]?.delegate
|
||||
del = instanceScope[c.mangledName(name)]?.delegate
|
||||
if (del != null) break
|
||||
}
|
||||
}
|
||||
@@ -97,7 +109,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
var targetRec = obj
|
||||
val d = decl ?: obj.declaringClass
|
||||
if (d != null) {
|
||||
val mangled = "${d.className}::$name"
|
||||
val mangled = d.mangledName(name)
|
||||
instanceScope.objects[mangled]?.let {
|
||||
targetRec = it
|
||||
}
|
||||
@@ -120,6 +132,24 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
willMutate(scope)
|
||||
val caller = scope.currentClassCtx
|
||||
|
||||
// Fast path for public members when outside any class context
|
||||
if (caller == null) {
|
||||
objClass.publicMemberResolution[name]?.let { key ->
|
||||
instanceScope.objects[key]?.let { rec ->
|
||||
if (rec.effectiveWriteVisibility == Visibility.Public) {
|
||||
// Skip property/delegated overhead if it's a plain mutable field
|
||||
if (rec.type == ObjRecord.Type.Field && rec.isMutable && !rec.isAbstract) {
|
||||
if (rec.value.assign(scope, newValue) == null)
|
||||
rec.value = newValue
|
||||
return
|
||||
}
|
||||
updateRecord(scope, rec, name, newValue, rec.declaringClass)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0. Private mangled of current class context
|
||||
caller?.let { c ->
|
||||
// Check for private methods/properties
|
||||
@@ -130,7 +160,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
}
|
||||
}
|
||||
// Check for private fields (stored in instanceScope)
|
||||
val mangled = "${c.className}::$name"
|
||||
val mangled = c.mangledName(name)
|
||||
instanceScope.objects[mangled]?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private) {
|
||||
updateRecord(scope, rec, name, newValue, c)
|
||||
@@ -142,7 +172,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
// 1. MRO mangled storage
|
||||
for (cls in objClass.mro) {
|
||||
if (cls.className == "Obj") break
|
||||
val mangled = "${cls.className}::$name"
|
||||
val mangled = cls.mangledName(name)
|
||||
instanceScope.objects[mangled]?.let { rec ->
|
||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, cls, caller)) {
|
||||
updateRecord(scope, rec, name, newValue, cls)
|
||||
@@ -191,24 +221,42 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
scope: Scope, name: String, args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
): Obj {
|
||||
// 0. Prefer private member of current class context
|
||||
scope.currentClassCtx?.let { caller ->
|
||||
val mangled = "${caller.className}::$name"
|
||||
instanceScope.objects[mangled]?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, caller)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(instanceScope, this, args, caller)
|
||||
val caller = scope.currentClassCtx
|
||||
|
||||
// Fast path for public members when outside any class context
|
||||
if (caller == null) {
|
||||
objClass.publicMemberResolution[name]?.let { key ->
|
||||
instanceScope.objects[key]?.let { rec ->
|
||||
if (rec.visibility == Visibility.Public && !rec.isAbstract) {
|
||||
val decl = rec.declaringClass
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(instanceScope, this, args, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
caller.members[name]?.let { rec ->
|
||||
}
|
||||
|
||||
// 0. Prefer private member of current class context
|
||||
caller?.let { c ->
|
||||
val mangled = c.mangledName(name)
|
||||
instanceScope.objects[mangled]?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, caller)
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(instanceScope, this, args, caller)
|
||||
return rec.value.invoke(instanceScope, this, args, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
c.members[name]?.let { rec ->
|
||||
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(instanceScope, this, args, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +268,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||
if (rec != null && !rec.isAbstract) {
|
||||
if (rec.type == ObjRecord.Type.Delegated) {
|
||||
val storageName = "${cls.className}::$name"
|
||||
val storageName = cls.mangledName(name)
|
||||
val del = instanceScope[storageName]?.delegate ?: rec.delegate
|
||||
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
|
||||
|
||||
@@ -233,8 +281,8 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
})
|
||||
}
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null
|
||||
if (!canAccessMember(rec.visibility, decl, effectiveCaller))
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -419,6 +419,25 @@ class MiniAstTest {
|
||||
assertEquals("Int", innerArg.segments.last().name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verify_class_member_structure() = runTest {
|
||||
val code = """
|
||||
class A {
|
||||
fun member() {}
|
||||
val field = 1
|
||||
}
|
||||
""".trimIndent()
|
||||
val (_, sink) = compileWithMini(code)
|
||||
val mini = sink.build()!!
|
||||
println("[DEBUG_LOG] Mini declarations: ${mini.declarations.map { it.name }}")
|
||||
assertEquals(1, mini.declarations.size, "Should only have one top-level declaration (class A)")
|
||||
val cls = mini.declarations[0] as MiniClassDecl
|
||||
assertEquals("A", cls.name)
|
||||
assertEquals(2, cls.members.size, "Class A should have 2 members (member() and field)")
|
||||
assertTrue(cls.members.any { it.name == "member" && it is MiniMemberFunDecl })
|
||||
assertTrue(cls.members.any { it.name == "field" && it is MiniMemberValDecl })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun inferTypeForValWithInference() = runTest {
|
||||
val code = """
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.miniast
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -462,6 +479,34 @@ class CompletionEngineLightTest {
|
||||
assertTrue(ns.contains("size"), "String member 'size' should be suggested for local x[0] inside set(), but got: $ns")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun functionArgumentsInBody() = runBlocking {
|
||||
val code = """
|
||||
fun test(myArg1, myArg2) {
|
||||
myA<caret>
|
||||
}
|
||||
""".trimIndent()
|
||||
val items = CompletionEngineLight.completeAtMarkerSuspend(code)
|
||||
val ns = names(items)
|
||||
assertTrue(ns.contains("myArg1"), "Function argument 'myArg1' should be proposed, but got: $ns")
|
||||
assertTrue(ns.contains("myArg2"), "Function argument 'myArg2' should be proposed, but got: $ns")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun methodArgumentsInBody() = runBlocking {
|
||||
val code = """
|
||||
class MyClass {
|
||||
fun test(myArg1, myArg2) {
|
||||
myA<caret>
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val items = CompletionEngineLight.completeAtMarkerSuspend(code)
|
||||
val ns = names(items)
|
||||
assertTrue(ns.contains("myArg1"), "Method argument 'myArg1' should be proposed, but got: $ns")
|
||||
assertTrue(ns.contains("myArg2"), "Method argument 'myArg2' should be proposed, but got: $ns")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nestedShadowingCompletion() = runBlocking {
|
||||
val code = """
|
||||
|
||||
Reference in new issue
Block a user