extern generics classes fix, documentation on extern classes updated
This commit is contained in:
parent
74eb8ff082
commit
8e0442670d
@ -245,7 +245,7 @@ This keeps Lyng semantics (visibility, overrides, type checks) in Lyng, while Ko
|
|||||||
|
|
||||||
Pure extern declarations use the simplified rule set:
|
Pure extern declarations use the simplified rule set:
|
||||||
- `extern class` / `extern object` are declaration-only ABI surfaces.
|
- `extern class` / `extern object` are declaration-only ABI surfaces.
|
||||||
- Every member in their body must be explicitly marked `extern`.
|
- 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.
|
- Plain Lyng member implementations inside `extern class` / `extern object` are not allowed.
|
||||||
- Put Lyng behavior into regular classes or extension methods.
|
- Put Lyng behavior into regular classes or extension methods.
|
||||||
|
|
||||||
@ -257,14 +257,14 @@ 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:
|
Example of pure extern class declaration:
|
||||||
|
|
||||||
```lyng
|
```lyng
|
||||||
extern class HostCounter {
|
extern class HostCounter {
|
||||||
extern var value: Int
|
var value: Int
|
||||||
extern fun inc(by: Int): Int
|
fun inc(by: Int): Int
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -342,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,7 +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 must be explicitly marked `extern`.
|
- **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.
|
||||||
|
|||||||
@ -20,7 +20,7 @@ The API is fixed and will be kept with further Lyng core changes. It is now the
|
|||||||
|
|
||||||
Extern declaration clarification:
|
Extern declaration clarification:
|
||||||
- `extern class` / `extern object` are pure extern surfaces.
|
- `extern class` / `extern object` are pure extern surfaces.
|
||||||
- Members inside them must be explicitly marked `extern`.
|
- 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.
|
- Lyng method/property bodies for these declarations should be implemented as extensions instead.
|
||||||
|
|
||||||
### Smart types system
|
### Smart types system
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "1.5.1-SNAPSHOT"
|
version = "1.5.0-SNAPSHOT"
|
||||||
|
|
||||||
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||||
|
|
||||||
|
|||||||
@ -7727,7 +7727,6 @@ class Compiler(
|
|||||||
|
|
||||||
val annotation = lastAnnotation
|
val annotation = lastAnnotation
|
||||||
val parentContext = codeContexts.last()
|
val parentContext = codeContexts.last()
|
||||||
val parentClassCtx = parentContext as? CodeContext.ClassBody
|
|
||||||
|
|
||||||
// Is extension?
|
// Is extension?
|
||||||
if (looksLikeExtensionReceiver()) {
|
if (looksLikeExtensionReceiver()) {
|
||||||
@ -7761,13 +7760,6 @@ class Compiler(
|
|||||||
name = t.value
|
name = t.value
|
||||||
nameStartPos = t.pos
|
nameStartPos = t.pos
|
||||||
}
|
}
|
||||||
if (parentClassCtx?.isExtern == true && !isExtern) {
|
|
||||||
val owner = parentClassCtx.name
|
|
||||||
throw ScriptError(
|
|
||||||
nameStartPos,
|
|
||||||
"member '$name' in extern class/object '$owner' must be declared extern (use `extern fun $name(...)`)"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val extensionWrapperName = extTypeName?.let { extensionCallableName(it, name) }
|
val extensionWrapperName = extTypeName?.let { extensionCallableName(it, name) }
|
||||||
val classCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
val classCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
||||||
var memberMethodId = if (extTypeName == null) classCtx?.memberMethodIds?.get(name) else null
|
var memberMethodId = if (extTypeName == null) classCtx?.memberMethodIds?.get(name) else null
|
||||||
@ -8790,16 +8782,6 @@ class Compiler(
|
|||||||
name = nameToken.value
|
name = nameToken.value
|
||||||
nameStartPos = nameToken.pos
|
nameStartPos = nameToken.pos
|
||||||
}
|
}
|
||||||
val parentClassCtx = codeContexts.lastOrNull() as? CodeContext.ClassBody
|
|
||||||
if (parentClassCtx?.isExtern == true && !isExtern) {
|
|
||||||
val owner = parentClassCtx.name
|
|
||||||
val kind = if (isMutable) "var" else "val"
|
|
||||||
throw ScriptError(
|
|
||||||
nameStartPos,
|
|
||||||
"member '$name' in extern class/object '$owner' must be declared extern (use `extern $kind $name: ...`)"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@ -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.*
|
||||||
@ -3097,19 +3097,6 @@ class ScriptTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testExternClassMembersMustBeExplicitlyExtern() = runTest {
|
|
||||||
assertFailsWith<ScriptError> {
|
|
||||||
eval(
|
|
||||||
"""
|
|
||||||
extern class HostClass {
|
|
||||||
fun doSomething(): Int
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testExternExtension() = runTest {
|
fun testExternExtension() = runTest {
|
||||||
eval(
|
eval(
|
||||||
|
|||||||
@ -621,24 +621,23 @@ class TypesTest {
|
|||||||
eval("""
|
eval("""
|
||||||
extern fun f<T>(x: T): T
|
extern fun f<T>(x: T): T
|
||||||
extern class Cell<T> {
|
extern class Cell<T> {
|
||||||
extern var value: T
|
var value: T
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testExternClassMemberMustBeExternMessage() = runTest {
|
fun testExternClassMemberInitializerStillFailsWithoutExplicitExtern() = runTest {
|
||||||
val e = assertFailsWith<ScriptError> {
|
val e = assertFailsWith<ScriptError> {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
extern class Cell<T> {
|
extern class Cell<T> {
|
||||||
var value: T
|
var value: T = 1
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
assertTrue(e.message?.contains("must be declared extern") == true)
|
assertTrue(e.message?.contains("extern variable value cannot have an initializer or delegate") == true)
|
||||||
assertTrue(e.message?.contains("extern var value") == true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test fun nonTrivialOperatorsTest() = runTest {
|
// @Test fun nonTrivialOperatorsTest() = runTest {
|
||||||
|
|||||||
@ -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
|
||||||
@ -13,7 +13,7 @@ This note describes the Lyng-first workflow where a class is declared in Lyng an
|
|||||||
- `instance.data` (per instance)
|
- `instance.data` (per instance)
|
||||||
- `classData` (per class)
|
- `classData` (per class)
|
||||||
- `extern class` / `extern object` are pure extern surfaces:
|
- `extern class` / `extern object` are pure extern surfaces:
|
||||||
- all members in their bodies must be explicitly `extern`;
|
- all members in their bodies are implicitly extern (`extern` is optional/redundant);
|
||||||
- Lyng member bodies inside extern classes/objects are not supported.
|
- Lyng member bodies inside extern classes/objects are not supported.
|
||||||
|
|
||||||
## Lyng: declare extern members
|
## Lyng: declare extern members
|
||||||
|
|||||||
@ -276,7 +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 must be explicitly marked `extern`.
|
- `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).
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user