Require explicit extern members in extern class/object and update docs
This commit is contained in:
parent
e447c778ed
commit
979e6ea9b7
@ -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 must be explicitly marked `extern`.
|
||||||
|
- 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 {
|
||||||
@ -253,6 +259,21 @@ 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 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`.
|
||||||
|
|
||||||
|
Example of pure extern class declaration:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
extern class HostCounter {
|
||||||
|
extern var value: Int
|
||||||
|
extern 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)
|
||||||
val moduleScope = Script.newScope() // or an existing module scope
|
val moduleScope = Script.newScope() // or an existing module scope
|
||||||
|
|||||||
@ -249,5 +249,6 @@ Lyng now provides a public Kotlin reflection bridge and a Lyng‑first class bin
|
|||||||
|
|
||||||
- **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 classes/members in Lyng (marked `extern`) 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`.
|
||||||
|
|
||||||
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 must be explicitly marked `extern`.
|
||||||
|
- 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.
|
||||||
|
|||||||
@ -7727,6 +7727,7 @@ 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()) {
|
||||||
@ -7760,6 +7761,9 @@ class Compiler(
|
|||||||
name = t.value
|
name = t.value
|
||||||
nameStartPos = t.pos
|
nameStartPos = t.pos
|
||||||
}
|
}
|
||||||
|
if (parentClassCtx?.isExtern == true && !isExtern) {
|
||||||
|
throw ScriptError(nameStartPos, "members of extern classes/objects must be marked extern")
|
||||||
|
}
|
||||||
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
|
||||||
@ -8782,6 +8786,10 @@ 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) {
|
||||||
|
throw ScriptError(nameStartPos, "members of extern classes/objects must be marked extern")
|
||||||
|
}
|
||||||
|
|
||||||
val receiverNormalization = normalizeReceiverTypeDecl(receiverTypeDecl, emptySet())
|
val receiverNormalization = normalizeReceiverTypeDecl(receiverTypeDecl, emptySet())
|
||||||
val implicitTypeParams = receiverNormalization.second
|
val implicitTypeParams = receiverNormalization.second
|
||||||
|
|||||||
@ -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> {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
@ -3097,6 +3097,19 @@ 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(
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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).
|
||||||
|
|||||||
@ -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 must be explicitly `extern`;
|
||||||
|
- 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 must be explicitly marked `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