diff --git a/docs/embedding.md b/docs/embedding.md index 119d89d..74b0fd6 100644 --- a/docs/embedding.md +++ b/docs/embedding.md @@ -245,7 +245,7 @@ This keeps Lyng semantics (visibility, overrides, type checks) in Lyng, while Ko 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`. +- 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. @@ -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: ```lyng extern class HostCounter { - extern var value: Int - extern fun inc(by: Int): Int + var value: Int + fun inc(by: Int): Int } ``` @@ -342,7 +342,7 @@ Notes: - Required order: declare/eval Lyng object in the module first, then call `bindObject(...)`. 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(...)`. Minimal `extern fun` example: diff --git a/docs/whats_new.md b/docs/whats_new.md index 0608495..0936e48 100644 --- a/docs/whats_new.md +++ b/docs/whats_new.md @@ -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: - **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. -- **Extern declaration rule**: `extern class` / `extern object` are declaration-only; all members in their bodies must be explicitly marked `extern`. +- **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. diff --git a/docs/whats_new_1_5.md b/docs/whats_new_1_5.md index 27935e0..e098eac 100644 --- a/docs/whats_new_1_5.md +++ b/docs/whats_new_1_5.md @@ -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 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. ### Smart types system diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index b5eaecb..daf1234 100644 --- a/lynglib/build.gradle.kts +++ b/lynglib/build.gradle.kts @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget 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 diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index a5baf62..b362d6d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -7727,7 +7727,6 @@ class Compiler( val annotation = lastAnnotation val parentContext = codeContexts.last() - val parentClassCtx = parentContext as? CodeContext.ClassBody // Is extension? if (looksLikeExtensionReceiver()) { @@ -7761,13 +7760,6 @@ class Compiler( name = t.value 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 classCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody var memberMethodId = if (extTypeName == null) classCtx?.memberMethodIds?.get(name) else null @@ -8790,16 +8782,6 @@ class Compiler( name = nameToken.value 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 implicitTypeParams = receiverNormalization.second if (implicitTypeParams.isNotEmpty()) pendingTypeParamStack.add(implicitTypeParams) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bridge/ClassBridge.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bridge/ClassBridge.kt index e81be16..2654c9a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bridge/ClassBridge.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bridge/ClassBridge.kt @@ -51,8 +51,9 @@ interface BridgeInstanceContext { * Use [LyngClassBridge.bind] to obtain a binder and register implementations. * 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 - * compiler emits the ABI slots that Kotlin bindings attach to. + * Important: members you bind here must be extern in Lyng (explicitly, or + * implicitly by being inside `extern class` / `extern object`) so the compiler + * emits the ABI slots that Kotlin bindings attach to. */ interface ClassBridgeBinder { /** Arbitrary Kotlin-side data attached to the class. */ @@ -70,7 +71,7 @@ interface ClassBridgeBinder { replaceWith = ReplaceWith("initWithInstance { block(this, thisObj) }") ) 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) /** * Legacy addFun form. @@ -81,7 +82,7 @@ interface ClassBridgeBinder { replaceWith = ReplaceWith("addFun(name) { impl(this, thisObj, args) }") ) 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) /** * Legacy addVal form. @@ -92,7 +93,7 @@ interface ClassBridgeBinder { replaceWith = ReplaceWith("addVal(name) { impl(this, thisObj) }") ) 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( name: String, get: suspend ScopeFacade.() -> Obj, @@ -119,8 +120,9 @@ interface ClassBridgeBinder { * Entry point for Kotlin bindings to declared Lyng classes. * * The workflow is Lyng-first: declare the class and its members in Lyng, - * then bind the implementations from Kotlin. Bound members must be marked - * `extern` so the compiler emits the ABI slots for Kotlin to attach to. + * then bind the implementations from Kotlin. Bound members must be extern + * (explicitly or by enclosing `extern class` / `extern object`) so the compiler + * emits the ABI slots for Kotlin to attach to. */ object LyngClassBridge { /** @@ -212,7 +214,7 @@ object LyngObjectBridge { /** * 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( className: String, @@ -222,7 +224,7 @@ suspend fun ModuleScope.bind( /** * 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( objectName: String, diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index a82950a..11c7799 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -30,8 +30,8 @@ import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.encodeToJsonElement import net.sergeych.lyng.* import net.sergeych.lyng.obj.* -import net.sergeych.lyng.thisAs import net.sergeych.lyng.pacman.InlineSourcesImportProvider +import net.sergeych.lyng.thisAs import net.sergeych.mp_tools.globalDefer import net.sergeych.tools.bm import kotlin.test.* @@ -3097,19 +3097,6 @@ class ScriptTest { ) } - @Test - fun testExternClassMembersMustBeExplicitlyExtern() = runTest { - assertFailsWith { - eval( - """ - extern class HostClass { - fun doSomething(): Int - } - """.trimIndent() - ) - } - } - @Test fun testExternExtension() = runTest { eval( diff --git a/lynglib/src/commonTest/kotlin/TypesTest.kt b/lynglib/src/commonTest/kotlin/TypesTest.kt index 36f7878..418b2f0 100644 --- a/lynglib/src/commonTest/kotlin/TypesTest.kt +++ b/lynglib/src/commonTest/kotlin/TypesTest.kt @@ -621,24 +621,23 @@ class TypesTest { eval(""" extern fun f(x: T): T extern class Cell { - extern var value: T + var value: T } """) } @Test - fun testExternClassMemberMustBeExternMessage() = runTest { + fun testExternClassMemberInitializerStillFailsWithoutExplicitExtern() = runTest { val e = assertFailsWith { eval( """ extern class Cell { - var value: T + var value: T = 1 } """.trimIndent() ) } - assertTrue(e.message?.contains("must be declared extern") == true) - assertTrue(e.message?.contains("extern var value") == true) + assertTrue(e.message?.contains("extern variable value cannot have an initializer or delegate") == true) } // @Test fun nonTrivialOperatorsTest() = runTest { diff --git a/notes/kotlin_bridge_binding.md b/notes/kotlin_bridge_binding.md index 5973c92..c533bce 100644 --- a/notes/kotlin_bridge_binding.md +++ b/notes/kotlin_bridge_binding.md @@ -4,7 +4,7 @@ This note describes the Lyng-first workflow where a class is declared in Lyng an ## 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(...)`. - Binding must happen **before the first instance is created**. - `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) - `classData` (per class) - `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: declare extern members diff --git a/notes/new_lyng_type_system_spec.md b/notes/new_lyng_type_system_spec.md index e417e6f..f066159 100644 --- a/notes/new_lyng_type_system_spec.md +++ b/notes/new_lyng_type_system_spec.md @@ -276,7 +276,7 @@ Map inference: - `{ "a": 1, "b": "x" }` is `Map`. - Empty map literal uses `{:}` (since `{}` is empty callable). - `extern class Map` so `Map()` is `Map()` 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: - Compiler should narrow types based on control-flow (e.g., `if (x != null)` narrows `x` to non-null inside the branch).