Compare commits
5 Commits
37d093817e
...
8e0442670d
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e0442670d | |||
| 74eb8ff082 | |||
| 2b13fe8053 | |||
| 979e6ea9b7 | |||
| e447c778ed |
@ -21,3 +21,7 @@
|
|||||||
- Create closure references only when a capture is detected; use a direct frame+slot reference (foreign slot ref) instead of scope slots.
|
- Create closure references only when a capture is detected; use a direct frame+slot reference (foreign slot ref) instead of scope slots.
|
||||||
- Keep Scope as a lazy reflection facade: resolve name -> slot only on demand for Kotlin interop (no eager name mapping on every call).
|
- Keep Scope as a lazy reflection facade: resolve name -> slot only on demand for Kotlin interop (no eager name mapping on every call).
|
||||||
- Avoid PUSH_SCOPE/POP_SCOPE in bytecode for loops/functions unless dynamic name access or Kotlin reflection is requested.
|
- Avoid PUSH_SCOPE/POP_SCOPE in bytecode for loops/functions unless dynamic name access or Kotlin reflection is requested.
|
||||||
|
|
||||||
|
## ABI proposal notes
|
||||||
|
- Runtime generic metadata for generic extern classes is tracked in `proposals/extern_generic_runtime_abi.md`.
|
||||||
|
- Keep this design `Obj`-centric: do not assume extern-class values are `ObjInstance`; collection must be enabled on `ObjClass`.
|
||||||
|
|||||||
@ -243,6 +243,12 @@ For extensions and libraries, the **preferred** workflow is Lyng‑first: declar
|
|||||||
|
|
||||||
This keeps Lyng semantics (visibility, overrides, type checks) in Lyng, while Kotlin supplies the behavior.
|
This keeps Lyng semantics (visibility, overrides, type checks) in Lyng, while Kotlin supplies the behavior.
|
||||||
|
|
||||||
|
Pure extern declarations use the simplified rule set:
|
||||||
|
- `extern class` / `extern object` are declaration-only ABI surfaces.
|
||||||
|
- Every member in their body is implicitly extern (you may still write `extern`, but it is redundant).
|
||||||
|
- Plain Lyng member implementations inside `extern class` / `extern object` are not allowed.
|
||||||
|
- Put Lyng behavior into regular classes or extension methods.
|
||||||
|
|
||||||
```lyng
|
```lyng
|
||||||
// Lyng side (in a module)
|
// Lyng side (in a module)
|
||||||
class Counter {
|
class Counter {
|
||||||
@ -251,7 +257,22 @@ class Counter {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: members must be marked `extern` so the compiler emits the ABI slots that Kotlin bindings attach to. This applies to functions and properties bound via `addFun` / `addVal` / `addVar`.
|
Note: members of `extern class` / `extern object` are treated as extern by default, so the compiler emits ABI slots that Kotlin bindings attach to. This applies to functions and properties bound via `addFun` / `addVal` / `addVar`.
|
||||||
|
|
||||||
|
Example of pure extern class declaration:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
extern class HostCounter {
|
||||||
|
var value: Int
|
||||||
|
fun inc(by: Int): Int
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need Lyng-side convenience behavior, add it as an extension:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
fun HostCounter.bump() = inc(1)
|
||||||
|
```
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
// Kotlin side (binding)
|
// Kotlin side (binding)
|
||||||
@ -321,7 +342,7 @@ Notes:
|
|||||||
|
|
||||||
- Required order: declare/eval Lyng object in the module first, then call `bindObject(...)`.
|
- Required order: declare/eval Lyng object in the module first, then call `bindObject(...)`.
|
||||||
This is the pattern covered by `BridgeBindingTest.testExternObjectBinding`.
|
This is the pattern covered by `BridgeBindingTest.testExternObjectBinding`.
|
||||||
- Members must be marked `extern` so the compiler emits ABI slots for Kotlin bindings.
|
- Members must be extern (explicitly, or implicitly via `extern object`) so the compiler emits ABI slots for Kotlin bindings.
|
||||||
- You can also bind by name/module via `LyngObjectBridge.bind(...)`.
|
- You can also bind by name/module via `LyngObjectBridge.bind(...)`.
|
||||||
|
|
||||||
Minimal `extern fun` example:
|
Minimal `extern fun` example:
|
||||||
|
|||||||
@ -248,6 +248,7 @@ The `Obj.getLyngExceptionMessageWithStackTrace()` extension method has been adde
|
|||||||
Lyng now provides a public Kotlin reflection bridge and a Lyng‑first class binding workflow. This is the **preferred** way to write Kotlin extensions and library integrations:
|
Lyng now provides a public Kotlin reflection bridge and a Lyng‑first class binding workflow. This is the **preferred** way to write Kotlin extensions and library integrations:
|
||||||
|
|
||||||
- **Bridge resolver**: explicit handles for values, vars, and callables with predictable lookup rules.
|
- **Bridge resolver**: explicit handles for values, vars, and callables with predictable lookup rules.
|
||||||
- **Class bridge binding**: declare classes/members in Lyng (marked `extern`) and bind the implementations in Kotlin before the first instance is created.
|
- **Class bridge binding**: declare extern surfaces in Lyng (`extern` members, or members inside `extern class/object`) and bind the implementations in Kotlin before the first instance is created.
|
||||||
|
- **Extern declaration rule**: `extern class` / `extern object` are declaration-only; all members in their bodies are implicitly extern.
|
||||||
|
|
||||||
See **Embedding Lyng** for full samples and usage details.
|
See **Embedding Lyng** for full samples and usage details.
|
||||||
|
|||||||
@ -18,6 +18,11 @@ In particular, it means no slow and flaky runtime lookups. Once compiled, code g
|
|||||||
|
|
||||||
The API is fixed and will be kept with further Lyng core changes. It is now the recommended way to write Lyng extensions in Kotlin. It is much simpler and more elegant than the internal one. See [Kotlin Bridge Binding](../notes/kotlin_bridge_binding.md).
|
The API is fixed and will be kept with further Lyng core changes. It is now the recommended way to write Lyng extensions in Kotlin. It is much simpler and more elegant than the internal one. See [Kotlin Bridge Binding](../notes/kotlin_bridge_binding.md).
|
||||||
|
|
||||||
|
Extern declaration clarification:
|
||||||
|
- `extern class` / `extern object` are pure extern surfaces.
|
||||||
|
- Members inside them are implicitly extern (`extern` on a member is optional/redundant).
|
||||||
|
- Lyng method/property bodies for these declarations should be implemented as extensions instead.
|
||||||
|
|
||||||
### Smart types system
|
### Smart types system
|
||||||
|
|
||||||
- **Deep inference**: The compiler analyzes types of symbols along the execution path and in many cases eliminates unnecessary casts or type specifications.
|
- **Deep inference**: The compiler analyzes types of symbols along the execution path and in many cases eliminates unnecessary casts or type specifications.
|
||||||
|
|||||||
@ -88,9 +88,11 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
|
|||||||
// Imports: each segment as namespace/path
|
// Imports: each segment as namespace/path
|
||||||
mini?.imports?.forEach { imp ->
|
mini?.imports?.forEach { imp ->
|
||||||
imp.segments.forEach { seg ->
|
imp.segments.forEach { seg ->
|
||||||
val start = analysis.source.offsetOf(seg.range.start)
|
if (seg.range.start.source === analysis.source && seg.range.end.source === analysis.source) {
|
||||||
val end = analysis.source.offsetOf(seg.range.end)
|
val start = analysis.source.offsetOf(seg.range.start)
|
||||||
putRange(start, end, LyngHighlighterColors.NAMESPACE)
|
val end = analysis.source.offsetOf(seg.range.end)
|
||||||
|
putRange(start, end, LyngHighlighterColors.NAMESPACE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,7 +35,7 @@ class LyngLexer : LexerBase() {
|
|||||||
"fun", "val", "var", "class", "interface", "type", "import", "as",
|
"fun", "val", "var", "class", "interface", "type", "import", "as",
|
||||||
"abstract", "closed", "override", "static", "extern", "open", "private", "protected",
|
"abstract", "closed", "override", "static", "extern", "open", "private", "protected",
|
||||||
"if", "else", "for", "while", "return", "true", "false", "null",
|
"if", "else", "for", "while", "return", "true", "false", "null",
|
||||||
"when", "in", "is", "break", "continue", "try", "catch", "finally",
|
"when", "in", "is", "break", "continue", "try", "catch", "finally", "void",
|
||||||
"get", "set", "object", "enum", "init", "by", "step", "property", "constructor"
|
"get", "set", "object", "enum", "init", "by", "step", "property", "constructor"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -26,23 +26,16 @@ import com.intellij.psi.search.FilenameIndex
|
|||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import net.sergeych.lyng.binding.BindingSnapshot
|
import net.sergeych.lyng.binding.BindingSnapshot
|
||||||
import net.sergeych.lyng.miniast.BuiltinDocRegistry
|
|
||||||
import net.sergeych.lyng.miniast.DocLookupUtils
|
|
||||||
import net.sergeych.lyng.miniast.MiniEnumDecl
|
|
||||||
import net.sergeych.lyng.miniast.MiniRange
|
|
||||||
import net.sergeych.lyng.miniast.MiniScript
|
|
||||||
import net.sergeych.lyng.tools.IdeLenientImportProvider
|
|
||||||
import net.sergeych.lyng.tools.LyngAnalysisRequest
|
|
||||||
import net.sergeych.lyng.tools.LyngAnalysisResult
|
|
||||||
import net.sergeych.lyng.tools.LyngDiagnostic
|
|
||||||
import net.sergeych.lyng.tools.LyngLanguageTools
|
|
||||||
import net.sergeych.lyng.idea.LyngFileType
|
import net.sergeych.lyng.idea.LyngFileType
|
||||||
|
import net.sergeych.lyng.miniast.*
|
||||||
|
import net.sergeych.lyng.tools.*
|
||||||
|
|
||||||
object LyngAstManager {
|
object LyngAstManager {
|
||||||
private val MINI_KEY = Key.create<MiniScript>("lyng.mini.cache")
|
private val MINI_KEY = Key.create<MiniScript>("lyng.mini.cache")
|
||||||
private val BINDING_KEY = Key.create<BindingSnapshot>("lyng.binding.cache")
|
private val BINDING_KEY = Key.create<BindingSnapshot>("lyng.binding.cache")
|
||||||
private val STAMP_KEY = Key.create<Long>("lyng.mini.cache.stamp")
|
private val STAMP_KEY = Key.create<Long>("lyng.mini.cache.stamp")
|
||||||
private val ANALYSIS_KEY = Key.create<LyngAnalysisResult>("lyng.analysis.cache")
|
private val ANALYSIS_KEY = Key.create<LyngAnalysisResult>("lyng.analysis.cache")
|
||||||
|
private val implicitBuiltinNames = setOf("void")
|
||||||
|
|
||||||
fun getMiniAst(file: PsiFile): MiniScript? = runReadAction {
|
fun getMiniAst(file: PsiFile): MiniScript? = runReadAction {
|
||||||
getAnalysis(file)?.mini
|
getAnalysis(file)?.mini
|
||||||
@ -217,7 +210,7 @@ object LyngAstManager {
|
|||||||
val msg = diag.message
|
val msg = diag.message
|
||||||
if (msg.startsWith("unresolved name: ")) {
|
if (msg.startsWith("unresolved name: ")) {
|
||||||
val name = msg.removePrefix("unresolved name: ").trim()
|
val name = msg.removePrefix("unresolved name: ").trim()
|
||||||
name in declaredTopLevel || name in builtinTopLevel
|
name in declaredTopLevel || name in builtinTopLevel || name in implicitBuiltinNames
|
||||||
} else if (msg.startsWith("unresolved member: ")) {
|
} else if (msg.startsWith("unresolved member: ")) {
|
||||||
val name = msg.removePrefix("unresolved member: ").trim()
|
val name = msg.removePrefix("unresolved member: ").trim()
|
||||||
val range = diag.range
|
val range = diag.range
|
||||||
|
|||||||
@ -124,4 +124,16 @@ class LyngDefinitionFilesTest : BasePlatformTestCase() {
|
|||||||
assertTrue("Should not report unresolved name for Declared", messages.none { it.contains("unresolved name: Declared") })
|
assertTrue("Should not report unresolved name for Declared", messages.none { it.contains("unresolved name: Declared") })
|
||||||
assertTrue("Should not report unresolved member for greet", messages.none { it.contains("unresolved member: greet") })
|
assertTrue("Should not report unresolved member for greet", messages.none { it.contains("unresolved member: greet") })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun test_DiagnosticsDoNotReportVoidAsUnresolvedName() {
|
||||||
|
val code = """
|
||||||
|
fun f(): void {
|
||||||
|
return void
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
myFixture.configureByText("main.lyng", code)
|
||||||
|
val analysis = LyngAstManager.getAnalysis(myFixture.file)
|
||||||
|
val messages = analysis?.diagnostics?.map { it.message } ?: emptyList()
|
||||||
|
assertTrue("Should not report unresolved name for void, got=$messages", messages.none { it.contains("unresolved name: void") })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -176,6 +176,8 @@ class Compiler(
|
|||||||
private val lambdaCaptureEntriesByRef: MutableMap<ValueFnRef, List<net.sergeych.lyng.bytecode.LambdaCaptureEntry>> =
|
private val lambdaCaptureEntriesByRef: MutableMap<ValueFnRef, List<net.sergeych.lyng.bytecode.LambdaCaptureEntry>> =
|
||||||
mutableMapOf()
|
mutableMapOf()
|
||||||
private val classFieldTypesByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf()
|
private val classFieldTypesByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf()
|
||||||
|
private val classMethodReturnTypeByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf()
|
||||||
|
private val classMethodReturnTypeDeclByName: MutableMap<String, MutableMap<String, TypeDecl>> = mutableMapOf()
|
||||||
private val classScopeMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
|
private val classScopeMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
|
||||||
private val classScopeCallableMembersByClassName: 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()
|
||||||
@ -2614,7 +2616,34 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.NOT -> {
|
Token.Type.NOT -> {
|
||||||
if (operand != null) throw ScriptError(t.pos, "unexpected operator not '!' ")
|
if (operand != null) {
|
||||||
|
val save = cc.savePos()
|
||||||
|
val next = cc.next()
|
||||||
|
if (next.type == Token.Type.NOT) {
|
||||||
|
val operandRef = operand
|
||||||
|
val receiverClass = resolveReceiverClassForMember(operandRef)
|
||||||
|
val inferredType = resolveReceiverTypeDecl(operandRef)
|
||||||
|
?: receiverClass?.let { TypeDecl.Simple(it.className, false) }
|
||||||
|
if (inferredType == null) {
|
||||||
|
operand = operandRef
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (inferredType == TypeDecl.TypeAny || inferredType == TypeDecl.TypeNullableAny) {
|
||||||
|
operand = operandRef
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val nonNullType = makeTypeDeclNonNullable(inferredType)
|
||||||
|
operand = CastRef(
|
||||||
|
operandRef,
|
||||||
|
TypeDeclRef(nonNullType, t.pos),
|
||||||
|
isNullable = false,
|
||||||
|
atPos = t.pos
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cc.restorePos(save)
|
||||||
|
throw ScriptError(t.pos, "unexpected operator not '!' ")
|
||||||
|
}
|
||||||
val op = parseTerm() ?: throw ScriptError(t.pos, "Expecting expression")
|
val op = parseTerm() ?: throw ScriptError(t.pos, "Expecting expression")
|
||||||
operand = UnaryOpRef(UnaryOp.NOT, op)
|
operand = UnaryOpRef(UnaryOp.NOT, op)
|
||||||
}
|
}
|
||||||
@ -3821,6 +3850,21 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun makeTypeDeclNonNullable(type: TypeDecl): TypeDecl {
|
||||||
|
if (!type.isNullable) return type
|
||||||
|
return when (type) {
|
||||||
|
TypeDecl.TypeAny -> type
|
||||||
|
TypeDecl.TypeNullableAny -> TypeDecl.TypeAny
|
||||||
|
is TypeDecl.Function -> type.copy(nullable = false)
|
||||||
|
is TypeDecl.Ellipsis -> type.copy(nullable = false)
|
||||||
|
is TypeDecl.TypeVar -> type.copy(nullable = false)
|
||||||
|
is TypeDecl.Union -> type.copy(nullable = false)
|
||||||
|
is TypeDecl.Intersection -> type.copy(nullable = false)
|
||||||
|
is TypeDecl.Simple -> TypeDecl.Simple(type.name, false)
|
||||||
|
is TypeDecl.Generic -> TypeDecl.Generic(type.name, type.args, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun makeMiniTypeNullable(type: MiniTypeRef): MiniTypeRef {
|
private fun makeMiniTypeNullable(type: MiniTypeRef): MiniTypeRef {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
is MiniTypeName -> type.copy(nullable = true)
|
is MiniTypeName -> type.copy(nullable = true)
|
||||||
@ -4491,6 +4535,48 @@ class Compiler(
|
|||||||
is TypeDecl.Intersection -> "I:${type.options.joinToString("&") { typeDeclKey(it) }}"
|
is TypeDecl.Intersection -> "I:${type.options.joinToString("&") { typeDeclKey(it) }}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun classMethodReturnTypeDecl(targetClass: ObjClass?, name: String): TypeDecl? {
|
||||||
|
if (targetClass == null) return null
|
||||||
|
if (targetClass == ObjDynamic.type) return TypeDecl.TypeAny
|
||||||
|
val member = targetClass.getInstanceMemberOrNull(name, includeAbstract = true)
|
||||||
|
val declaringName = member?.declaringClass?.className
|
||||||
|
if (declaringName != null) {
|
||||||
|
classMethodReturnTypeDeclByName[declaringName]?.get(name)?.let { return it }
|
||||||
|
classMethodReturnTypeByName[declaringName]?.get(name)?.let {
|
||||||
|
return TypeDecl.Simple(it.className, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
classMethodReturnTypeDeclByName[targetClass.className]?.get(name)?.let { return it }
|
||||||
|
classMethodReturnTypeByName[targetClass.className]?.get(name)?.let {
|
||||||
|
return TypeDecl.Simple(it.className, false)
|
||||||
|
}
|
||||||
|
member?.typeDecl?.let { declaredType ->
|
||||||
|
if (declaredType is TypeDecl.Function) return declaredType.returnType
|
||||||
|
return declaredType
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun classMethodReturnClass(targetClass: ObjClass?, name: String): ObjClass? {
|
||||||
|
if (targetClass == null) return null
|
||||||
|
if (targetClass == ObjDynamic.type) return ObjDynamic.type
|
||||||
|
classMethodReturnTypeDecl(targetClass, name)?.let { declared ->
|
||||||
|
resolveTypeDeclObjClass(declared)?.let { return it }
|
||||||
|
if (declared is TypeDecl.TypeVar) return Obj.rootObjectType
|
||||||
|
}
|
||||||
|
val member = targetClass.getInstanceMemberOrNull(name, includeAbstract = true)
|
||||||
|
val declaringName = member?.declaringClass?.className
|
||||||
|
if (declaringName != null) {
|
||||||
|
classMethodReturnTypeByName[declaringName]?.get(name)?.let { return it }
|
||||||
|
}
|
||||||
|
classMethodReturnTypeByName[targetClass.className]?.get(name)?.let { return it }
|
||||||
|
val declaredType = member?.typeDecl
|
||||||
|
if (declaredType is TypeDecl.Function) {
|
||||||
|
resolveTypeDeclObjClass(declaredType.returnType)?.let { return it }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private fun inferObjClassFromRef(ref: ObjRef): ObjClass? = when (ref) {
|
private fun inferObjClassFromRef(ref: ObjRef): ObjClass? = when (ref) {
|
||||||
is ConstRef -> ref.constValue as? ObjClass ?: ref.constValue.objClass
|
is ConstRef -> ref.constValue as? ObjClass ?: ref.constValue.objClass
|
||||||
is LocalVarRef -> nameObjClass[ref.name] ?: resolveClassByName(ref.name)
|
is LocalVarRef -> nameObjClass[ref.name] ?: resolveClassByName(ref.name)
|
||||||
@ -4511,6 +4597,11 @@ class Compiler(
|
|||||||
is RangeRef -> ObjRange.type
|
is RangeRef -> ObjRange.type
|
||||||
is ClassOperatorRef -> ObjClassType
|
is ClassOperatorRef -> ObjClassType
|
||||||
is CastRef -> resolveTypeRefClass(ref.castTypeRef())
|
is CastRef -> resolveTypeRefClass(ref.castTypeRef())
|
||||||
|
is IndexRef -> {
|
||||||
|
val targetClass = resolveReceiverClassForMember(ref.targetRef)
|
||||||
|
classMethodReturnClass(targetClass, "getAt")
|
||||||
|
?: inferFieldReturnClass(targetClass, "getAt")
|
||||||
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4547,6 +4638,12 @@ class Compiler(
|
|||||||
else -> TypeDecl.TypeVar("${typeDeclName(targetDecl)}.${ref.name}", false)
|
else -> TypeDecl.TypeVar("${typeDeclName(targetDecl)}.${ref.name}", false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is IndexRef -> {
|
||||||
|
val targetDecl = resolveReceiverTypeDecl(ref.targetRef)
|
||||||
|
inferMethodCallReturnTypeDecl("getAt", targetDecl)?.let { return it }
|
||||||
|
val targetClass = resolveReceiverClassForMember(ref.targetRef)
|
||||||
|
classMethodReturnTypeDecl(targetClass, "getAt")
|
||||||
|
}
|
||||||
is MethodCallRef -> methodReturnTypeDeclByRef[ref]
|
is MethodCallRef -> methodReturnTypeDeclByRef[ref]
|
||||||
is CallRef -> callReturnTypeDeclByRef[ref]
|
is CallRef -> callReturnTypeDeclByRef[ref]
|
||||||
is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverTypeDecl(it.ref) }
|
is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverTypeDecl(it.ref) }
|
||||||
@ -4616,6 +4713,12 @@ class Compiler(
|
|||||||
val targetClass = resolveReceiverClassForMember(ref.target)
|
val targetClass = resolveReceiverClassForMember(ref.target)
|
||||||
inferFieldReturnClass(targetClass, ref.name)
|
inferFieldReturnClass(targetClass, ref.name)
|
||||||
}
|
}
|
||||||
|
is IndexRef -> {
|
||||||
|
val targetClass = resolveReceiverClassForMember(ref.targetRef)
|
||||||
|
classMethodReturnClass(targetClass, "getAt")
|
||||||
|
?: inferFieldReturnClass(targetClass, "getAt")
|
||||||
|
?: inferMethodCallReturnClass("getAt")
|
||||||
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5102,6 +5205,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveTypeRefClass(ref: ObjRef): ObjClass? = when (ref) {
|
private fun resolveTypeRefClass(ref: ObjRef): ObjClass? = when (ref) {
|
||||||
|
is TypeDeclRef -> resolveTypeDeclObjClass(ref.decl())
|
||||||
is ConstRef -> ref.constValue as? ObjClass
|
is ConstRef -> ref.constValue as? ObjClass
|
||||||
is LocalSlotRef -> resolveTypeDeclObjClass(TypeDecl.Simple(ref.name, false)) ?: nameObjClass[ref.name]
|
is LocalSlotRef -> resolveTypeDeclObjClass(TypeDecl.Simple(ref.name, false)) ?: nameObjClass[ref.name]
|
||||||
is LocalVarRef -> resolveTypeDeclObjClass(TypeDecl.Simple(ref.name, false)) ?: nameObjClass[ref.name]
|
is LocalVarRef -> resolveTypeDeclObjClass(TypeDecl.Simple(ref.name, false)) ?: nameObjClass[ref.name]
|
||||||
@ -7885,6 +7989,23 @@ class Compiler(
|
|||||||
val rawFnStatements = parsedFnStatements?.let { unwrapBytecodeDeep(it) }
|
val rawFnStatements = parsedFnStatements?.let { unwrapBytecodeDeep(it) }
|
||||||
val inferredReturnClass = returnTypeDecl?.let { resolveTypeDeclObjClass(it) }
|
val inferredReturnClass = returnTypeDecl?.let { resolveTypeDeclObjClass(it) }
|
||||||
?: inferReturnClassFromStatement(rawFnStatements)
|
?: inferReturnClassFromStatement(rawFnStatements)
|
||||||
|
if (declKind == SymbolKind.MEMBER && extTypeName == null) {
|
||||||
|
val ownerClassName = (parentContext as? CodeContext.ClassBody)?.name
|
||||||
|
if (ownerClassName != null) {
|
||||||
|
val returnDecl = returnTypeDecl
|
||||||
|
?: inferredReturnClass?.let { TypeDecl.Simple(it.className, false) }
|
||||||
|
if (returnDecl != null) {
|
||||||
|
classMethodReturnTypeDeclByName
|
||||||
|
.getOrPut(ownerClassName) { mutableMapOf() }[name] = returnDecl
|
||||||
|
resolveTypeDeclObjClass(returnDecl)?.let { returnClass ->
|
||||||
|
classMethodReturnTypeByName
|
||||||
|
.getOrPut(ownerClassName) { mutableMapOf() }[name] = returnClass
|
||||||
|
classFieldTypesByName
|
||||||
|
.getOrPut(ownerClassName) { mutableMapOf() }[name] = returnClass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (declKind != SymbolKind.MEMBER && inferredReturnClass != null) {
|
if (declKind != SymbolKind.MEMBER && inferredReturnClass != null) {
|
||||||
callableReturnTypeByName[name] = inferredReturnClass
|
callableReturnTypeByName[name] = inferredReturnClass
|
||||||
val slotLoc = lookupSlotLocation(name, includeModule = true)
|
val slotLoc = lookupSlotLocation(name, includeModule = true)
|
||||||
@ -8661,7 +8782,6 @@ class Compiler(
|
|||||||
name = nameToken.value
|
name = nameToken.value
|
||||||
nameStartPos = nameToken.pos
|
nameStartPos = nameToken.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
val receiverNormalization = normalizeReceiverTypeDecl(receiverTypeDecl, emptySet())
|
val receiverNormalization = normalizeReceiverTypeDecl(receiverTypeDecl, emptySet())
|
||||||
val implicitTypeParams = receiverNormalization.second
|
val implicitTypeParams = receiverNormalization.second
|
||||||
if (implicitTypeParams.isNotEmpty()) pendingTypeParamStack.add(implicitTypeParams)
|
if (implicitTypeParams.isNotEmpty()) pendingTypeParamStack.add(implicitTypeParams)
|
||||||
|
|||||||
@ -51,8 +51,9 @@ interface BridgeInstanceContext {
|
|||||||
* Use [LyngClassBridge.bind] to obtain a binder and register implementations.
|
* Use [LyngClassBridge.bind] to obtain a binder and register implementations.
|
||||||
* Bindings must happen before the first instance of the class is created.
|
* Bindings must happen before the first instance of the class is created.
|
||||||
*
|
*
|
||||||
* Important: members you bind here must be declared as `extern` in Lyng so the
|
* Important: members you bind here must be extern in Lyng (explicitly, or
|
||||||
* compiler emits the ABI slots that Kotlin bindings attach to.
|
* implicitly by being inside `extern class` / `extern object`) so the compiler
|
||||||
|
* emits the ABI slots that Kotlin bindings attach to.
|
||||||
*/
|
*/
|
||||||
interface ClassBridgeBinder {
|
interface ClassBridgeBinder {
|
||||||
/** Arbitrary Kotlin-side data attached to the class. */
|
/** Arbitrary Kotlin-side data attached to the class. */
|
||||||
@ -70,7 +71,7 @@ interface ClassBridgeBinder {
|
|||||||
replaceWith = ReplaceWith("initWithInstance { block(this, thisObj) }")
|
replaceWith = ReplaceWith("initWithInstance { block(this, thisObj) }")
|
||||||
)
|
)
|
||||||
fun initWithInstance(block: suspend (ScopeFacade, Obj) -> Unit)
|
fun initWithInstance(block: suspend (ScopeFacade, Obj) -> Unit)
|
||||||
/** Bind a Lyng function/member to a Kotlin implementation (requires `extern` in Lyng). */
|
/** Bind a Lyng function/member to a Kotlin implementation (requires extern member in Lyng). */
|
||||||
fun addFun(name: String, impl: suspend ScopeFacade.() -> Obj)
|
fun addFun(name: String, impl: suspend ScopeFacade.() -> Obj)
|
||||||
/**
|
/**
|
||||||
* Legacy addFun form.
|
* Legacy addFun form.
|
||||||
@ -81,7 +82,7 @@ interface ClassBridgeBinder {
|
|||||||
replaceWith = ReplaceWith("addFun(name) { impl(this, thisObj, args) }")
|
replaceWith = ReplaceWith("addFun(name) { impl(this, thisObj, args) }")
|
||||||
)
|
)
|
||||||
fun addFun(name: String, impl: suspend (ScopeFacade, Obj, Arguments) -> Obj)
|
fun addFun(name: String, impl: suspend (ScopeFacade, Obj, Arguments) -> Obj)
|
||||||
/** Bind a read-only member (val/property getter) declared as `extern`. */
|
/** Bind a read-only member (val/property getter) declared extern in Lyng. */
|
||||||
fun addVal(name: String, impl: suspend ScopeFacade.() -> Obj)
|
fun addVal(name: String, impl: suspend ScopeFacade.() -> Obj)
|
||||||
/**
|
/**
|
||||||
* Legacy addVal form.
|
* Legacy addVal form.
|
||||||
@ -92,7 +93,7 @@ interface ClassBridgeBinder {
|
|||||||
replaceWith = ReplaceWith("addVal(name) { impl(this, thisObj) }")
|
replaceWith = ReplaceWith("addVal(name) { impl(this, thisObj) }")
|
||||||
)
|
)
|
||||||
fun addVal(name: String, impl: suspend (ScopeFacade, Obj) -> Obj)
|
fun addVal(name: String, impl: suspend (ScopeFacade, Obj) -> Obj)
|
||||||
/** Bind a mutable member (var/property getter/setter) declared as `extern`. */
|
/** Bind a mutable member (var/property getter/setter) declared extern in Lyng. */
|
||||||
fun addVar(
|
fun addVar(
|
||||||
name: String,
|
name: String,
|
||||||
get: suspend ScopeFacade.() -> Obj,
|
get: suspend ScopeFacade.() -> Obj,
|
||||||
@ -119,8 +120,9 @@ interface ClassBridgeBinder {
|
|||||||
* Entry point for Kotlin bindings to declared Lyng classes.
|
* Entry point for Kotlin bindings to declared Lyng classes.
|
||||||
*
|
*
|
||||||
* The workflow is Lyng-first: declare the class and its members in Lyng,
|
* The workflow is Lyng-first: declare the class and its members in Lyng,
|
||||||
* then bind the implementations from Kotlin. Bound members must be marked
|
* then bind the implementations from Kotlin. Bound members must be extern
|
||||||
* `extern` so the compiler emits the ABI slots for Kotlin to attach to.
|
* (explicitly or by enclosing `extern class` / `extern object`) so the compiler
|
||||||
|
* emits the ABI slots for Kotlin to attach to.
|
||||||
*/
|
*/
|
||||||
object LyngClassBridge {
|
object LyngClassBridge {
|
||||||
/**
|
/**
|
||||||
@ -212,7 +214,7 @@ object LyngObjectBridge {
|
|||||||
/**
|
/**
|
||||||
* Sugar for [LyngClassBridge.bind] on a module scope.
|
* Sugar for [LyngClassBridge.bind] on a module scope.
|
||||||
*
|
*
|
||||||
* Bound members must be declared as `extern` in Lyng.
|
* Bound members must be extern in Lyng (explicitly or via enclosing extern class/object).
|
||||||
*/
|
*/
|
||||||
suspend fun ModuleScope.bind(
|
suspend fun ModuleScope.bind(
|
||||||
className: String,
|
className: String,
|
||||||
@ -222,7 +224,7 @@ suspend fun ModuleScope.bind(
|
|||||||
/**
|
/**
|
||||||
* Sugar for [LyngObjectBridge.bind] on a module scope.
|
* Sugar for [LyngObjectBridge.bind] on a module scope.
|
||||||
*
|
*
|
||||||
* Bound members must be declared as `extern` in Lyng.
|
* Bound members must be extern in Lyng (explicitly or via enclosing extern class/object).
|
||||||
*/
|
*/
|
||||||
suspend fun ModuleScope.bindObject(
|
suspend fun ModuleScope.bindObject(
|
||||||
objectName: String,
|
objectName: String,
|
||||||
|
|||||||
@ -46,7 +46,7 @@ private val fallbackKeywordIds = setOf(
|
|||||||
"private", "protected", "static", "open", "extern", "init", "get", "set", "by", "step",
|
"private", "protected", "static", "open", "extern", "init", "get", "set", "by", "step",
|
||||||
// control flow and misc
|
// control flow and misc
|
||||||
"if", "else", "when", "while", "do", "for", "try", "catch", "finally",
|
"if", "else", "when", "while", "do", "for", "try", "catch", "finally",
|
||||||
"throw", "return", "break", "continue", "this", "null", "true", "false", "unset"
|
"throw", "return", "break", "continue", "this", "null", "true", "false", "unset", "void"
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Maps lexer token type (and sometimes value) to a [HighlightKind]. */
|
/** Maps lexer token type (and sometimes value) to a [HighlightKind]. */
|
||||||
|
|||||||
@ -24,46 +24,46 @@ package lyng.observable
|
|||||||
extern class ChangeRejectionException : Exception
|
extern class ChangeRejectionException : Exception
|
||||||
|
|
||||||
extern class Subscription {
|
extern class Subscription {
|
||||||
fun cancel(): Void
|
extern fun cancel(): Void
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class Observable<Change> {
|
extern class Observable<Change> {
|
||||||
fun beforeChange(listener: (Change)->Void): Subscription
|
extern fun beforeChange(listener: (Change)->Void): Subscription
|
||||||
fun onChange(listener: (Change)->Void): Subscription
|
extern fun onChange(listener: (Change)->Void): Subscription
|
||||||
fun changes(): Flow<Change>
|
extern fun changes(): Flow<Change>
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class ListChange<T>
|
extern class ListChange<T>
|
||||||
|
|
||||||
extern class ListSet<T> : ListChange<T> {
|
extern class ListSet<T> : ListChange<T> {
|
||||||
val index: Int
|
extern val index: Int
|
||||||
val oldValue: Object
|
extern val oldValue: Object
|
||||||
val newValue: Object
|
extern val newValue: Object
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class ListInsert<T> : ListChange<T> {
|
extern class ListInsert<T> : ListChange<T> {
|
||||||
val index: Int
|
extern val index: Int
|
||||||
val values: List<T>
|
extern val values: List<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class ListRemove<T> : ListChange<T> {
|
extern class ListRemove<T> : ListChange<T> {
|
||||||
val index: Int
|
extern val index: Int
|
||||||
val oldValue: Object
|
extern val oldValue: Object
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class ListClear<T> : ListChange<T> {
|
extern class ListClear<T> : ListChange<T> {
|
||||||
val oldValues: List<T>
|
extern val oldValues: List<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class ListReorder<T> : ListChange<T> {
|
extern class ListReorder<T> : ListChange<T> {
|
||||||
val oldValues: List<T>
|
extern val oldValues: List<T>
|
||||||
val newValues: Object
|
extern val newValues: Object
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class ObservableList<T> : List<T> {
|
extern class ObservableList<T> : List<T> {
|
||||||
fun beforeChange(listener: (ListChange<T>)->Void): Subscription
|
extern fun beforeChange(listener: (ListChange<T>)->Void): Subscription
|
||||||
fun onChange(listener: (ListChange<T>)->Void): Subscription
|
extern fun onChange(listener: (ListChange<T>)->Void): Subscription
|
||||||
fun changes(): Flow<ListChange<T>>
|
extern fun changes(): Flow<ListChange<T>>
|
||||||
}
|
}
|
||||||
|
|
||||||
fun List<T>.observable(): ObservableList<T> {
|
fun List<T>.observable(): ObservableList<T> {
|
||||||
|
|||||||
@ -179,6 +179,7 @@ object LyngLanguageTools {
|
|||||||
val source = analysis.source
|
val source = analysis.source
|
||||||
val out = ArrayList<LyngSemanticSpan>(128)
|
val out = ArrayList<LyngSemanticSpan>(128)
|
||||||
val covered = HashSet<Pair<Int, Int>>()
|
val covered = HashSet<Pair<Int, Int>>()
|
||||||
|
fun isCurrentSource(pos: Pos): Boolean = pos.source === source
|
||||||
|
|
||||||
fun addRange(start: Int, end: Int, kind: LyngSemanticKind) {
|
fun addRange(start: Int, end: Int, kind: LyngSemanticKind) {
|
||||||
if (start < 0 || end <= start || end > analysis.text.length) return
|
if (start < 0 || end <= start || end > analysis.text.length) return
|
||||||
@ -187,6 +188,7 @@ object LyngLanguageTools {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addName(pos: Pos, name: String, kind: LyngSemanticKind) {
|
fun addName(pos: Pos, name: String, kind: LyngSemanticKind) {
|
||||||
|
if (!isCurrentSource(pos)) return
|
||||||
val s = source.offsetOf(pos)
|
val s = source.offsetOf(pos)
|
||||||
addRange(s, s + name.length, kind)
|
addRange(s, s + name.length, kind)
|
||||||
}
|
}
|
||||||
@ -206,7 +208,9 @@ object LyngLanguageTools {
|
|||||||
addTypeSegments(t.returnType)
|
addTypeSegments(t.returnType)
|
||||||
}
|
}
|
||||||
is MiniTypeVar -> {
|
is MiniTypeVar -> {
|
||||||
addRange(source.offsetOf(t.range.start), source.offsetOf(t.range.end), LyngSemanticKind.TypeRef)
|
if (isCurrentSource(t.range.start) && isCurrentSource(t.range.end)) {
|
||||||
|
addRange(source.offsetOf(t.range.start), source.offsetOf(t.range.end), LyngSemanticKind.TypeRef)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is MiniTypeUnion -> {
|
is MiniTypeUnion -> {
|
||||||
t.options.forEach { addTypeSegments(it) }
|
t.options.forEach { addTypeSegments(it) }
|
||||||
@ -262,7 +266,9 @@ object LyngLanguageTools {
|
|||||||
|
|
||||||
mini.imports.forEach { imp ->
|
mini.imports.forEach { imp ->
|
||||||
imp.segments.forEach { seg ->
|
imp.segments.forEach { seg ->
|
||||||
addRange(source.offsetOf(seg.range.start), source.offsetOf(seg.range.end), LyngSemanticKind.TypeRef)
|
if (isCurrentSource(seg.range.start) && isCurrentSource(seg.range.end)) {
|
||||||
|
addRange(source.offsetOf(seg.range.start), source.offsetOf(seg.range.end), LyngSemanticKind.TypeRef)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -164,7 +164,7 @@ class BindingTest {
|
|||||||
val ms = Script.newScope()
|
val ms = Script.newScope()
|
||||||
ms.eval("""
|
ms.eval("""
|
||||||
extern class A {
|
extern class A {
|
||||||
fun get1(): String
|
extern fun get1(): String
|
||||||
}
|
}
|
||||||
|
|
||||||
extern fun getA(): A
|
extern fun getA(): A
|
||||||
@ -184,4 +184,3 @@ class BindingTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -231,7 +231,7 @@ class BridgeBindingTest {
|
|||||||
val ms = Script.newScope()
|
val ms = Script.newScope()
|
||||||
ms.eval("""
|
ms.eval("""
|
||||||
extern class A {
|
extern class A {
|
||||||
val field: Int
|
extern val field: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test(a: A) = a.field
|
fun test(a: A) = a.field
|
||||||
|
|||||||
@ -249,13 +249,13 @@ class MiniAstTest {
|
|||||||
// Doc2
|
// Doc2
|
||||||
extern class C1 {
|
extern class C1 {
|
||||||
// Doc3
|
// Doc3
|
||||||
fun m1()
|
extern fun m1()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doc4
|
// Doc4
|
||||||
extern object O1 {
|
extern object O1 {
|
||||||
// Doc5
|
// Doc5
|
||||||
val v1: String
|
extern val v1: String
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doc6
|
// Doc6
|
||||||
|
|||||||
@ -30,8 +30,8 @@ import kotlinx.serialization.json.JsonPrimitive
|
|||||||
import kotlinx.serialization.json.encodeToJsonElement
|
import kotlinx.serialization.json.encodeToJsonElement
|
||||||
import net.sergeych.lyng.*
|
import net.sergeych.lyng.*
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.thisAs
|
|
||||||
import net.sergeych.lyng.pacman.InlineSourcesImportProvider
|
import net.sergeych.lyng.pacman.InlineSourcesImportProvider
|
||||||
|
import net.sergeych.lyng.thisAs
|
||||||
import net.sergeych.mp_tools.globalDefer
|
import net.sergeych.mp_tools.globalDefer
|
||||||
import net.sergeych.tools.bm
|
import net.sergeych.tools.bm
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
@ -3080,11 +3080,11 @@ class ScriptTest {
|
|||||||
"""
|
"""
|
||||||
extern fun hostFunction(a: Int, b: String): String
|
extern fun hostFunction(a: Int, b: String): String
|
||||||
extern class HostClass(name: String) {
|
extern class HostClass(name: String) {
|
||||||
fun doSomething(): Int
|
extern fun doSomething(): Int
|
||||||
val status: String
|
extern val status: String
|
||||||
}
|
}
|
||||||
extern object HostObject {
|
extern object HostObject {
|
||||||
fun getInstance(): HostClass
|
extern fun getInstance(): HostClass
|
||||||
}
|
}
|
||||||
extern enum HostEnum {
|
extern enum HostEnum {
|
||||||
VALUE1, VALUE2
|
VALUE1, VALUE2
|
||||||
|
|||||||
@ -565,19 +565,6 @@ class TypesTest {
|
|||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
|
||||||
// fun testNullableGenericTypes() = runTest {
|
|
||||||
// eval("""
|
|
||||||
// fun t<T>(): String =
|
|
||||||
// when(T) {
|
|
||||||
// is Object -> "%s is Object"(T::class.name)
|
|
||||||
// else -> throw "It should not happen"
|
|
||||||
// }
|
|
||||||
// assert( Int is Object)
|
|
||||||
// assertEquals( t<Int>(), "Class is Object")
|
|
||||||
// """.trimIndent())
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Test fun testIndexer() = runTest {
|
@Test fun testIndexer() = runTest {
|
||||||
eval("""
|
eval("""
|
||||||
class Greeter {
|
class Greeter {
|
||||||
@ -596,6 +583,63 @@ class TypesTest {
|
|||||||
|
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test fun testIndexer2() = runTest {
|
||||||
|
eval("""
|
||||||
|
class Foo(bar)
|
||||||
|
|
||||||
|
class Greeter {
|
||||||
|
override fun getAt(name): Foo = Foo("Hello, %s!"(name))
|
||||||
|
}
|
||||||
|
assertEquals("Hello, Bob!",Greeter()["Bob"].bar)
|
||||||
|
val g = Greeter()
|
||||||
|
assertEquals("Hello, Bob!",g["Bob"].bar)
|
||||||
|
|
||||||
|
// it should work with objects too:
|
||||||
|
object Polite {
|
||||||
|
override fun getAt(name): Foo? = Foo("How do you do, %s?"(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals("How do you do, Bob?",Polite["Bob"].bar)
|
||||||
|
assertEquals("How do you do, Bob?",Polite["Bob"]?.bar)
|
||||||
|
assertEquals("How do you do, Bob?",Polite["Bob"]!!.bar)
|
||||||
|
|
||||||
|
class Greeter2 {
|
||||||
|
override fun getAt(name): Foo? = Foo("How do you do, %s?"(name))
|
||||||
|
}
|
||||||
|
val g2 = Greeter2()
|
||||||
|
assertEquals("How do you do, Bob?",g2["Bob"]?.bar)
|
||||||
|
val g2v: Foo = g2["Bob"]!!
|
||||||
|
assertEquals("How do you do, Bob?",g2v.bar)
|
||||||
|
assertEquals("How do you do, Bob?",Greeter2()["Bob"].bar)
|
||||||
|
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExternGenerics() = runTest {
|
||||||
|
eval("""
|
||||||
|
extern fun f<T>(x: T): T
|
||||||
|
extern class Cell<T> {
|
||||||
|
var value: T
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExternClassMemberInitializerStillFailsWithoutExplicitExtern() = runTest {
|
||||||
|
val e = assertFailsWith<ScriptError> {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
extern class Cell<T> {
|
||||||
|
var value: T = 1
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
assertTrue(e.message?.contains("extern variable value cannot have an initializer or delegate") == true)
|
||||||
|
}
|
||||||
|
|
||||||
// @Test fun nonTrivialOperatorsTest() = runTest {
|
// @Test fun nonTrivialOperatorsTest() = runTest {
|
||||||
// val s = Script.newScope()
|
// val s = Script.newScope()
|
||||||
// s.eval("""
|
// s.eval("""
|
||||||
|
|||||||
@ -18,8 +18,9 @@
|
|||||||
package net.sergeych.lyng.tools
|
package net.sergeych.lyng.tools
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.lyng.miniast.MiniClassDecl
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.miniast.MiniMemberTypeAliasDecl
|
import net.sergeych.lyng.Source
|
||||||
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.stdlib_included.rootLyng
|
import net.sergeych.lyng.stdlib_included.rootLyng
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -127,4 +128,48 @@ class LyngLanguageToolsTest {
|
|||||||
val dis = LyngLanguageTools.disassembleSymbol(code, "add")
|
val dis = LyngLanguageTools.disassembleSymbol(code, "add")
|
||||||
assertTrue(!dis.contains("not a compiled body"), "Disassembly should be produced, got: $dis")
|
assertTrue(!dis.contains("not a compiled body"), "Disassembly should be produced, got: $dis")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun languageTools_semanticHighlights_ignore_foreign_sources() {
|
||||||
|
val localSource = Source("local.lyng", "val x = 1")
|
||||||
|
val foreignSource = Source("defs.lyng.d", "val y = 2")
|
||||||
|
val localStart = Pos(localSource, 0, 0)
|
||||||
|
val foreignStart = Pos(foreignSource, 0, 0)
|
||||||
|
|
||||||
|
val mini = MiniScript(
|
||||||
|
range = MiniRange(localStart, localStart),
|
||||||
|
declarations = mutableListOf(
|
||||||
|
MiniValDecl(
|
||||||
|
range = MiniRange(foreignStart, foreignStart),
|
||||||
|
name = "y",
|
||||||
|
mutable = false,
|
||||||
|
type = null,
|
||||||
|
initRange = null,
|
||||||
|
doc = null,
|
||||||
|
nameStart = foreignStart
|
||||||
|
)
|
||||||
|
),
|
||||||
|
imports = mutableListOf(
|
||||||
|
MiniImport(
|
||||||
|
range = MiniRange(foreignStart, foreignStart),
|
||||||
|
segments = listOf(
|
||||||
|
MiniImport.Segment("defs", MiniRange(foreignStart, foreignStart))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val analysis = LyngAnalysisResult(
|
||||||
|
source = localSource,
|
||||||
|
text = localSource.text,
|
||||||
|
mini = mini,
|
||||||
|
binding = null,
|
||||||
|
resolution = null,
|
||||||
|
importedModules = emptyList(),
|
||||||
|
diagnostics = emptyList(),
|
||||||
|
lexicalHighlights = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
val spans = LyngLanguageTools.semanticHighlights(analysis)
|
||||||
|
assertTrue(spans.isEmpty(), "Semantic spans should ignore positions from foreign sources, got $spans")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -209,7 +209,7 @@ class CompletionEngineLightTest {
|
|||||||
fun inferredTypeFromMemberCall() = runBlocking {
|
fun inferredTypeFromMemberCall() = runBlocking {
|
||||||
val code = """
|
val code = """
|
||||||
extern class MyClass {
|
extern class MyClass {
|
||||||
fun getList(): List<String>
|
extern fun getList(): List<String>
|
||||||
}
|
}
|
||||||
extern val c: MyClass
|
extern val c: MyClass
|
||||||
val x = c.getList()
|
val x = c.getList()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package lyng.stdlib
|
package lyng.stdlib
|
||||||
|
|
||||||
extern fun flow(builder: FlowBuilder.()->Void): Flow
|
extern fun flow(builder: FlowBuilder.()->void): Flow
|
||||||
|
|
||||||
/* Built-in exception type. */
|
/* Built-in exception type. */
|
||||||
extern class Exception
|
extern class Exception
|
||||||
@ -8,22 +8,22 @@ extern class IllegalArgumentException
|
|||||||
extern class NotImplementedException
|
extern class NotImplementedException
|
||||||
extern class Delegate
|
extern class Delegate
|
||||||
extern class Iterable<T> {
|
extern class Iterable<T> {
|
||||||
fun iterator(): Iterator<T>
|
extern fun iterator(): Iterator<T>
|
||||||
fun forEach(action: (T)->Void): Void
|
extern fun forEach(action: (T)->void): void
|
||||||
fun map<R>(transform: (T)->R): List<R>
|
extern fun map<R>(transform: (T)->R): List<R>
|
||||||
fun toList(): List<T>
|
extern fun toList(): List<T>
|
||||||
fun toImmutableList(): ImmutableList<T>
|
extern fun toImmutableList(): ImmutableList<T>
|
||||||
val toSet: Set<T>
|
extern val toSet: Set<T>
|
||||||
val toImmutableSet: ImmutableSet<T>
|
extern val toImmutableSet: ImmutableSet<T>
|
||||||
val toMap: Map<Object,Object>
|
extern val toMap: Map<Object,Object>
|
||||||
val toImmutableMap: ImmutableMap<Object,Object>
|
extern val toImmutableMap: ImmutableMap<Object,Object>
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class Iterator<T> {
|
extern class Iterator<T> {
|
||||||
fun hasNext(): Bool
|
extern fun hasNext(): Bool
|
||||||
fun next(): T
|
extern fun next(): T
|
||||||
fun cancelIteration(): Void
|
extern fun cancelIteration(): void
|
||||||
fun toList(): List<T>
|
extern fun toList(): List<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Host-provided iterator wrapper for Kotlin collections.
|
// Host-provided iterator wrapper for Kotlin collections.
|
||||||
@ -33,47 +33,47 @@ class KotlinIterator<T> : Iterator<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern class Collection<T> : Iterable<T> {
|
extern class Collection<T> : Iterable<T> {
|
||||||
val size: Int
|
extern val size: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class Array<T> : Collection<T> {
|
extern class Array<T> : Collection<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class ImmutableList<T> : Array<T> {
|
extern class ImmutableList<T> : Array<T> {
|
||||||
fun toMutable(): List<T>
|
extern fun toMutable(): List<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class List<T> : Array<T> {
|
extern class List<T> : Array<T> {
|
||||||
fun add(value: T, more...): Void
|
extern fun add(value: T, more...): void
|
||||||
fun toImmutable(): ImmutableList<T>
|
extern fun toImmutable(): ImmutableList<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class RingBuffer<T> : Iterable<T> {
|
extern class RingBuffer<T> : Iterable<T> {
|
||||||
val size: Int
|
extern val size: Int
|
||||||
fun first(): T
|
extern fun first(): T
|
||||||
fun add(value: T): Void
|
extern fun add(value: T): void
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class Set<T> : Collection<T> {
|
extern class Set<T> : Collection<T> {
|
||||||
fun toImmutable(): ImmutableSet<T>
|
extern fun toImmutable(): ImmutableSet<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class ImmutableSet<T> : Collection<T> {
|
extern class ImmutableSet<T> : Collection<T> {
|
||||||
fun toMutable(): Set<T>
|
extern fun toMutable(): Set<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class Map<K,V> : Collection<MapEntry<K,V>> {
|
extern class Map<K,V> : Collection<MapEntry<K,V>> {
|
||||||
fun toImmutable(): ImmutableMap<K,V>
|
extern fun toImmutable(): ImmutableMap<K,V>
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class ImmutableMap<K,V> : Collection<MapEntry<K,V>> {
|
extern class ImmutableMap<K,V> : Collection<MapEntry<K,V>> {
|
||||||
fun getOrNull(key: K): V?
|
extern fun getOrNull(key: K): V?
|
||||||
fun toMutable(): Map<K,V>
|
extern fun toMutable(): Map<K,V>
|
||||||
}
|
}
|
||||||
|
|
||||||
extern class MapEntry<K,V> : Array<Object> {
|
extern class MapEntry<K,V> : Array<Object> {
|
||||||
val key: K
|
extern val key: K
|
||||||
val value: V
|
extern val value: V
|
||||||
}
|
}
|
||||||
|
|
||||||
// Built-in math helpers (implemented in host runtime).
|
// Built-in math helpers (implemented in host runtime).
|
||||||
@ -337,17 +337,17 @@ override fun List<T>.toString() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Sort list in-place by key selector. */
|
/* Sort list in-place by key selector. */
|
||||||
fun List<T>.sortBy<R>(predicate: (T)->R): Void {
|
fun List<T>.sortBy<R>(predicate: (T)->R): void {
|
||||||
sortWith { a, b -> predicate(a) <=> predicate(b) }
|
sortWith { a, b -> predicate(a) <=> predicate(b) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sort list in-place by natural order. */
|
/* Sort list in-place by natural order. */
|
||||||
fun List<T>.sort(): Void {
|
fun List<T>.sort(): void {
|
||||||
sortWith { a, b -> a <=> b }
|
sortWith { a, b -> a <=> b }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Print this exception and its stack trace to standard output. */
|
/* Print this exception and its stack trace to standard output. */
|
||||||
fun Exception.printStackTrace(): Void {
|
fun Exception.printStackTrace(): void {
|
||||||
println(this)
|
println(this)
|
||||||
for( entry in stackTrace ) {
|
for( entry in stackTrace ) {
|
||||||
println("\tat "+entry.toString())
|
println("\tat "+entry.toString())
|
||||||
@ -357,7 +357,7 @@ fun Exception.printStackTrace(): Void {
|
|||||||
/* Compile this string into a regular expression. */
|
/* Compile this string into a regular expression. */
|
||||||
val String.re: Regex get() = Regex(this)
|
val String.re: Regex get() = Regex(this)
|
||||||
|
|
||||||
fun TODO(message: Object?=null): Void {
|
fun TODO(message: Object?=null): void {
|
||||||
throw "not implemented"
|
throw "not implemented"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,12 +376,12 @@ enum DelegateAccess {
|
|||||||
Implementing this interface is optional as Lyng uses dynamic dispatch,
|
Implementing this interface is optional as Lyng uses dynamic dispatch,
|
||||||
but it is recommended for documentation and clarity.
|
but it is recommended for documentation and clarity.
|
||||||
*/
|
*/
|
||||||
interface Delegate<T,ThisRefType=Void> {
|
interface Delegate<T,ThisRefType=void> {
|
||||||
/* Called when a delegated 'val' or 'var' is read. */
|
/* Called when a delegated 'val' or 'var' is read. */
|
||||||
fun getValue(thisRef: ThisRefType, name: String): T = TODO("delegate getter is not implemented")
|
fun getValue(thisRef: ThisRefType, name: String): T = TODO("delegate getter is not implemented")
|
||||||
|
|
||||||
/* Called when a delegated 'var' is written. */
|
/* Called when a delegated 'var' is written. */
|
||||||
fun setValue(thisRef: ThisRefType, name: String, newValue: T): Void = TODO("delegate setter is not implemented")
|
fun setValue(thisRef: ThisRefType, name: String, newValue: T): void = TODO("delegate setter is not implemented")
|
||||||
|
|
||||||
/* Called when a delegated function is invoked. */
|
/* Called when a delegated function is invoked. */
|
||||||
fun invoke(thisRef: ThisRefType, name: String, args...): Object = TODO("delegate invoke is not implemented")
|
fun invoke(thisRef: ThisRefType, name: String, args...): Object = TODO("delegate invoke is not implemented")
|
||||||
|
|||||||
@ -4,7 +4,7 @@ This note describes the Lyng-first workflow where a class is declared in Lyng an
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
- Lyng code declares a class and marks members as `extern`.
|
- Lyng code declares a class and marks members as `extern` (or puts them inside `extern class`/`extern object`, where member `extern` is implicit).
|
||||||
- Kotlin binds implementations with `LyngClassBridge.bind(...)`.
|
- Kotlin binds implementations with `LyngClassBridge.bind(...)`.
|
||||||
- Binding must happen **before the first instance is created**.
|
- Binding must happen **before the first instance is created**.
|
||||||
- `bind(className, module, importManager)` requires `module` to resolve class names; use
|
- `bind(className, module, importManager)` requires `module` to resolve class names; use
|
||||||
@ -12,6 +12,9 @@ This note describes the Lyng-first workflow where a class is declared in Lyng an
|
|||||||
- Kotlin can store two opaque payloads:
|
- Kotlin can store two opaque payloads:
|
||||||
- `instance.data` (per instance)
|
- `instance.data` (per instance)
|
||||||
- `classData` (per class)
|
- `classData` (per class)
|
||||||
|
- `extern class` / `extern object` are pure extern surfaces:
|
||||||
|
- all members in their bodies are implicitly extern (`extern` is optional/redundant);
|
||||||
|
- Lyng member bodies inside extern classes/objects are not supported.
|
||||||
|
|
||||||
## Lyng: declare extern members
|
## Lyng: declare extern members
|
||||||
|
|
||||||
@ -70,3 +73,4 @@ LyngClassBridge.bind(className = "Foo", module = "bridge.mod", importManager = i
|
|||||||
- Use `init { ... }` / `initWithInstance { ... }` with `ScopeFacade` receiver; access instance via `thisObj`.
|
- Use `init { ... }` / `initWithInstance { ... }` with `ScopeFacade` receiver; access instance via `thisObj`.
|
||||||
- `classData` and `instance.data` are Kotlin-only payloads and do not appear in Lyng reflection.
|
- `classData` and `instance.data` are Kotlin-only payloads and do not appear in Lyng reflection.
|
||||||
- Binding after the first instance of a class is created throws a `ScriptError`.
|
- Binding after the first instance of a class is created throws a `ScriptError`.
|
||||||
|
- If you need Lyng-side helpers for an extern type, add them as extensions, e.g. `fun Foo.helper() = ...`.
|
||||||
|
|||||||
@ -276,6 +276,7 @@ Map inference:
|
|||||||
- `{ "a": 1, "b": "x" }` is `Map<String,Int|String>`.
|
- `{ "a": 1, "b": "x" }` is `Map<String,Int|String>`.
|
||||||
- Empty map literal uses `{:}` (since `{}` is empty callable).
|
- Empty map literal uses `{:}` (since `{}` is empty callable).
|
||||||
- `extern class Map<K=String,V=Object>` so `Map()` is `Map<String,Object>()` unless contextual type overrides.
|
- `extern class Map<K=String,V=Object>` so `Map()` is `Map<String,Object>()` unless contextual type overrides.
|
||||||
|
- `extern class` / `extern object` are declaration-only; members in their bodies are implicitly extern.
|
||||||
|
|
||||||
Flow typing:
|
Flow typing:
|
||||||
- Compiler should narrow types based on control-flow (e.g., `if (x != null)` narrows `x` to non-null inside the branch).
|
- Compiler should narrow types based on control-flow (e.g., `if (x != null)` narrows `x` to non-null inside the branch).
|
||||||
|
|||||||
179
proposals/extern_generic_runtime_abi.md
Normal file
179
proposals/extern_generic_runtime_abi.md
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
# Proposal: Runtime Generic Metadata for Extern Classes (Obj-Centric ABI)
|
||||||
|
|
||||||
|
Status: Draft
|
||||||
|
Date: 2026-03-15
|
||||||
|
Owner: compiler/runtime/bridge
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
`extern class` declarations are currently allowed to be generic (e.g. `extern class Cell<T>`), and this is already used by stdlib (`Iterable<T>`, `List<T>`, `Map<K,V>`, etc.).
|
||||||
|
|
||||||
|
The open problem is exposing applied generic type arguments to Kotlin-side bindings at runtime.
|
||||||
|
|
||||||
|
Important constraint:
|
||||||
|
- Extern-class values are **not necessarily** `ObjInstance`.
|
||||||
|
- The only hard requirement is that values are `Obj`.
|
||||||
|
|
||||||
|
So the ABI design must be `Obj`-centric and must not assume any specific object implementation layout.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Keep `extern class<T>` allowed.
|
||||||
|
- Make runtime generic metadata collection explicit and opt-in.
|
||||||
|
- Support all `Obj` subclasses, including host/custom objects not backed by `ObjInstance`.
|
||||||
|
- Provide a stable Kotlin bridge API for reading applied type args.
|
||||||
|
- Keep default runtime overhead near zero for classes that do not need this feature.
|
||||||
|
|
||||||
|
## Non-goals (phase 1)
|
||||||
|
|
||||||
|
- Full reified method-generic metadata for every call site.
|
||||||
|
- Runtime enforcement of generic bounds from metadata alone.
|
||||||
|
- Backfilling metadata for pre-existing objects created without capture.
|
||||||
|
|
||||||
|
## Proposed ABI Shape
|
||||||
|
|
||||||
|
### 1) Opt-in flag on `ObjClass`
|
||||||
|
|
||||||
|
Add runtime policy to `ObjClass`:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
enum class RuntimeGenericMode {
|
||||||
|
None,
|
||||||
|
CaptureClassTypeArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
open class ObjClass(...) : Obj() {
|
||||||
|
open val runtimeGenericMode: RuntimeGenericMode = RuntimeGenericMode.None
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Default remains `None`.
|
||||||
|
|
||||||
|
### 2) Per-object metadata storage owned by `ObjClass`
|
||||||
|
|
||||||
|
Store metadata keyed by `Obj` identity, not by instance internals:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
open class ObjClass(...) : Obj() {
|
||||||
|
open fun setRuntimeTypeArgs(obj: Obj, args: List<RuntimeTypeRef>)
|
||||||
|
open fun getRuntimeTypeArgs(obj: Obj): List<RuntimeTypeRef>?
|
||||||
|
open fun clearRuntimeTypeArgs(obj: Obj) // optional
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Default implementation:
|
||||||
|
- class-local identity map keyed by `Obj` references.
|
||||||
|
- no assumptions about object field layout.
|
||||||
|
- works for any `Obj` subtype.
|
||||||
|
|
||||||
|
Implementation note:
|
||||||
|
- JVM: weak identity map preferred to avoid leaks.
|
||||||
|
- JS/Wasm/Native: platform-appropriate identity map strategy; weak where available.
|
||||||
|
|
||||||
|
### 3) Type token format
|
||||||
|
|
||||||
|
Introduce compact runtime token:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
sealed class RuntimeTypeRef {
|
||||||
|
data class Simple(val className: String, val nullable: Boolean = false) : RuntimeTypeRef()
|
||||||
|
data class Generic(val className: String, val args: List<RuntimeTypeRef>, val nullable: Boolean = false) : RuntimeTypeRef()
|
||||||
|
data class Union(val options: List<RuntimeTypeRef>, val nullable: Boolean = false) : RuntimeTypeRef()
|
||||||
|
data class Intersection(val options: List<RuntimeTypeRef>, val nullable: Boolean = false) : RuntimeTypeRef()
|
||||||
|
data class TypeVar(val name: String, val nullable: Boolean = false) : RuntimeTypeRef()
|
||||||
|
data class Unknown(val nullable: Boolean = false) : RuntimeTypeRef()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`Unknown` is required so arity is preserved even when inference cannot produce a concrete runtime type.
|
||||||
|
|
||||||
|
### 4) Compiler/runtime collection point
|
||||||
|
|
||||||
|
When constructing/obtaining a value of generic class `C<...>`:
|
||||||
|
- Resolve applied class type arguments (explicit or inferred).
|
||||||
|
- If target class has `runtimeGenericMode == CaptureClassTypeArgs`, call:
|
||||||
|
- `C.setRuntimeTypeArgs(resultObj, resolvedArgsAsRuntimeTypeRefs)`
|
||||||
|
- If mode is `None`, do nothing.
|
||||||
|
|
||||||
|
No assumption about concrete returned object type besides `Obj`.
|
||||||
|
|
||||||
|
### 5) Bridge accessor API
|
||||||
|
|
||||||
|
Add a stable helper on binding context/facade:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun typeArgsOf(obj: Obj, asClass: ObjClass): List<RuntimeTypeRef>?
|
||||||
|
fun typeArgOf(obj: Obj, asClass: ObjClass, index: Int): RuntimeTypeRef?
|
||||||
|
```
|
||||||
|
|
||||||
|
Semantics:
|
||||||
|
- Reads metadata stored under `asClass`.
|
||||||
|
- Returns `null` when absent/not captured.
|
||||||
|
- Does not throw on non-`ObjInstance` objects.
|
||||||
|
|
||||||
|
This allows a host binding for `extern class Cell<T>` to inspect `T` safely.
|
||||||
|
|
||||||
|
## Source-Level Policy
|
||||||
|
|
||||||
|
No syntax change required in phase 1.
|
||||||
|
|
||||||
|
How to enable capture:
|
||||||
|
- Kotlin host sets `runtimeGenericMode` for classes that need it.
|
||||||
|
- Optionally add compiler directive later (future work), but not required for MVP.
|
||||||
|
|
||||||
|
## ABI Compatibility
|
||||||
|
|
||||||
|
- Backward compatible by default (`None` mode does not change behavior).
|
||||||
|
- Older runtimes can ignore metadata-related calls if feature not used.
|
||||||
|
- Optional capability flag can be introduced (`GENERIC_RUNTIME_ARGS`) for explicit runtime negotiation.
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
- Zero overhead for classes with `None`.
|
||||||
|
- For capture-enabled classes: one map write per object creation/registration event.
|
||||||
|
- Memory cost proportional to number of live captured objects and type token size.
|
||||||
|
- Prefer weak-key maps where possible to minimize retention risk.
|
||||||
|
|
||||||
|
## Failure Modes and Handling
|
||||||
|
|
||||||
|
- If type args cannot be fully resolved at capture point:
|
||||||
|
- store `Unknown` tokens in unresolved positions.
|
||||||
|
- If an object is produced outside normal constructor flow:
|
||||||
|
- bridge can call `setRuntimeTypeArgs` manually if needed.
|
||||||
|
- If map strategy is unavailable on a platform:
|
||||||
|
- provide best-effort fallback map and document lifetime caveats.
|
||||||
|
|
||||||
|
## Phased Implementation Plan
|
||||||
|
|
||||||
|
### Phase 1 (MVP)
|
||||||
|
- Add `RuntimeGenericMode` on `ObjClass`.
|
||||||
|
- Add `RuntimeTypeRef`.
|
||||||
|
- Add `ObjClass` metadata storage/getters.
|
||||||
|
- Collect class-level generic args for opted-in extern classes.
|
||||||
|
- Add bridge getters and tests with non-`ObjInstance` `Obj` subtype.
|
||||||
|
|
||||||
|
### Phase 2
|
||||||
|
- Inheritance-aware views (querying args for parent extern classes if needed).
|
||||||
|
- Better token normalization/canonicalization.
|
||||||
|
|
||||||
|
### Phase 3
|
||||||
|
- Optional method-level generic call metadata (if a concrete use-case appears).
|
||||||
|
|
||||||
|
## Test Plan (minimum)
|
||||||
|
|
||||||
|
- `extern class Cell<T>` with capture enabled:
|
||||||
|
- `Cell<Int>()` exposes `Int`.
|
||||||
|
- inferred type args captured where available.
|
||||||
|
- Same class with capture disabled:
|
||||||
|
- no metadata returned.
|
||||||
|
- Non-`ObjInstance` host object path:
|
||||||
|
- metadata can be set/read via `ObjClass` API.
|
||||||
|
- Unknown/incomplete inference:
|
||||||
|
- returned args preserve arity via `Unknown`.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- Exact cross-platform weak identity map abstraction location.
|
||||||
|
- Whether to add user-facing Lyng syntax for enabling capture, or keep host-only policy.
|
||||||
|
- Whether parent-generic projection should be materialized at capture time or computed lazily.
|
||||||
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
> under cosntruction
|
|
||||||
> note to AI: ignore this file for now
|
|
||||||
|
|
||||||
The problem is, sometimes we want to implement an interface enhancing existing classes. For example, we want ti implement Delegate for Map. We can add extension methods to Map that do the work, but we can add Delegate to the inheritance chain.
|
|
||||||
|
|
||||||
The problem is not trivial: while adding interfaces in other languages is easy, adding the while
|
|
||||||
class with a state to existing one should be done carefully.
|
|
||||||
|
|
||||||
Proposed syntax:
|
|
||||||
|
|
||||||
```lyng
|
|
||||||
extend Map with Delegate {
|
|
||||||
fun getValue(thisRef, key) = this[key]
|
|
||||||
fun setValue(thisRef, key, value) = this[key] = value
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And now we can use Map as a Delegate:
|
|
||||||
```lyng
|
|
||||||
val map = { foo: 1. bar: 2 }
|
|
||||||
val foo by map
|
|
||||||
assertEquals(1, foo)
|
|
||||||
```
|
|
||||||
|
|
||||||
The syntax is similar to the one used for inheritance. But while Delegate has no state and it is actually simple. Much harder task is ti implement some class with state (trait):
|
|
||||||
|
|
||||||
```lyng
|
|
||||||
// the class we will use as a trait must have on constructor parameters
|
|
||||||
// or only parameters with default values
|
|
||||||
class MyTraitClass(initValue=100) {
|
|
||||||
private var field
|
|
||||||
fun traitField get() = field + initValue
|
|
||||||
set(value) { field = value }
|
|
||||||
}
|
|
||||||
|
|
||||||
extend Map with MyTraitClass
|
|
||||||
|
|
||||||
assertEquals(100, Map().traitField)
|
|
||||||
val m = Map()
|
|
||||||
m.traitField = 1000
|
|
||||||
assertEquals(1100,m.traitField)
|
|
||||||
```
|
|
||||||
|
|
||||||
We limit extension to module scope level, e.g., not in functions, not in classes, but at the "global level", probably ModuleScope.
|
|
||||||
|
|
||||||
The course of action could be:
|
|
||||||
|
|
||||||
- when constructing a class instance, compiler search in the ModuleScope extensions for it, and if found, add them to MI parent list to the end in the order of appearance in code (e.g. random ;)), them construct the instance as usual.
|
|
||||||
Loading…
x
Reference in New Issue
Block a user