user exceptions to kotlin fixes

This commit is contained in:
sergeych committed 2026-01-08 09:21:25 +01:00
1 parent 1d089db9ff
commit c12804a806
9 files changed
+262 -13

No files matched your search

@@ -20,6 +20,7 @@ package net.sergeych.lyng
import net.sergeych.lyng.Compiler.Companion.compile
import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.pacman.ImportProvider
/**
@@ -3840,4 +3841,6 @@ class Compiler(
}
suspend fun eval(code: String) = compile(code).execute()
suspend fun evalNamed(name: String, code: String, importManager: ImportManager = Script.defaultImportManager) =
compile(Source(name,code), importManager).execute()
@@ -67,6 +67,10 @@ open class Obj {
someClass == rootObjectType ||
(someClass is ObjClass && objClass.allImplementingNames.contains(someClass.className))
fun isInstanceOf(className: String) =
objClass.mro.any { it.className == className } ||
objClass.allImplementingNames.contains(className)
suspend fun invokeInstanceMethod(scope: Scope, name: String, vararg args: Obj): Obj =
invokeInstanceMethod(scope, name, Arguments(args.toList()))
@@ -159,20 +159,17 @@ open class ObjException(
}
val Root = ExceptionClass("Exception").apply {
instanceConstructor = statement {
val msg = args.getOrNull(0) ?: ObjString("Exception")
if (thisObj is ObjInstance) {
(thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg)
}
ObjVoid
}
instanceInitializers.add(statement {
if (thisObj is ObjInstance) {
val msg = get("message")?.value ?: ObjString("Exception")
(thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg)
val stack = captureStackTrace(this)
(thisObj as ObjInstance).instanceScope.addItem("Exception::stackTrace", false, stack)
}
ObjVoid
})
instanceConstructor = statement { ObjVoid }
addConstDoc(
name = "message",
value = statement {
@@ -331,3 +328,43 @@ class ObjUnsetException(scope: Scope, message: String = "property is unset (not
class ObjNotImplementedException(scope: Scope, message: String = "not implemented") :
ObjException("NotImplementedException", scope, message)
/**
* Check if the object is an instance of Lyng Exception class.
*/
fun Obj.isLyngException(): Boolean = isInstanceOf("Exception")
/**
* Get the exception message.
*/
suspend fun Obj.getLyngExceptionMessage(scope: Scope): String =
invokeInstanceMethod(scope, "message").toString(scope).value
/**
* Get the exception stack trace.
*/
suspend fun Obj.getLyngExceptionStackTrace(scope: Scope): ObjList =
invokeInstanceMethod(scope, "stackTrace").cast(scope)
/**
* Get the exception extra data.
*/
suspend fun Obj.getLyngExceptionExtraData(scope: Scope): Obj =
invokeInstanceMethod(scope, "extraData")
/**
* Get the exception as a formatted string with the primary throw site.
*/
suspend fun Obj.getLyngExceptionString(scope: Scope): String =
invokeInstanceMethod(scope, "toString").toString(scope).value
/**
* Rethrow this object as a Kotlin [ExecutionError] if it's an exception.
*/
suspend fun Obj.raiseAsExecutionError(scope: Scope?=null): Nothing {
if (this is ObjException) raise()
val sc = scope ?: Script.newScope()
val msg = getLyngExceptionMessage(sc)
val pos = (this as? ObjInstance)?.instanceScope?.pos ?: Pos.builtIn
throw ExecutionError(this, pos, msg)
}
@@ -294,10 +294,12 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// }
val serializingVars: Map<String, ObjRecord> by lazy {
val metaParams = objClass.constructorMeta?.params?.map { it.name }?.toSet() ?: emptySet()
instanceScope.objects.filter {
it.value.type.serializable &&
it.value.type == ObjRecord.Type.Field &&
it.value.isMutable
it.value.isMutable &&
!metaParams.contains(it.key)
}
}
@@ -0,0 +1,121 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.obj.*
import net.sergeych.lynon.lynonDecodeAny
import net.sergeych.lynon.lynonEncodeAny
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertTrue
class EmbeddingExceptionTest {
@Test
fun testExceptionMessageSerialization() = runTest {
val scope = Script.newScope()
val ex = scope.eval("Exception(\"test message\")")
val encoded = lynonEncodeAny(scope, ex)
val decoded = lynonDecodeAny(scope, encoded)
assertEquals("test message", decoded.getLyngExceptionMessage(scope))
}
@Test
fun testIsInstanceOfString() = runTest {
val scope = Script.newScope()
scope.eval("class T")
val t = scope.eval("T()")
assertTrue(t.isInstanceOf("T"))
assertTrue(t.isInstanceOf("Obj"))
}
@Test
fun testExceptionSerializationAndRethrow() = runTest {
val scope = Script.newScope()
// 1. Define, throw and catch the exception in Lyng to get the object
val errorObj = scope.eval("""
class MyException(val code, m) : Exception(m)
try {
throw MyException(123, "something failed")
} catch {
it
}
""".trimIndent())
assertTrue(errorObj.isLyngException(), "Should be a Lyng exception")
assertTrue(errorObj.isInstanceOf("MyException"), "Should be a MyException")
// 2. Serialize it
val encoded = lynonEncodeAny(scope, errorObj)
// 3. Deserialize it
val decodedObj = lynonDecodeAny(scope, encoded)
// 4. Rethrow it using the new uniform extension
val rethrown = try {
decodedObj.raiseAsExecutionError(scope)
} catch (e: ExecutionError) {
e
}
// 5. Verify the rethrown exception preserves its identity and data
val caughtObj = rethrown.errorObject
assertTrue(caughtObj.isLyngException())
assertTrue(caughtObj.isInstanceOf("MyException"))
assertIs<ObjInstance>(caughtObj)
assertEquals("MyException", caughtObj.objClass.className)
// Verify we can still access the custom fields
// 'code' is a field, so we use readField
val code = caughtObj.readField(scope, "code").value.toKotlin(scope)
assertEquals(123L, code)
val message = caughtObj.getLyngExceptionMessage(scope)
assertEquals("something failed", message)
// Verify stack trace is available
val stack = caughtObj.getLyngExceptionStackTrace(scope)
assertTrue(stack.list.isNotEmpty(), "Stack trace should not be empty")
val errorString = caughtObj.getLyngExceptionString(scope)
assertTrue(errorString.contains("MyException: something failed"), "Error string should contain message")
}
@Test
fun testBuiltInExceptionSerialization() = runTest {
val scope = Script.newScope()
val encoded = try {
scope.eval("throw IllegalArgumentException(\"bad arg\")")
null
} catch (e: ExecutionError) {
lynonEncodeAny(scope, e.errorObject)
}!!
val decodedObj = lynonDecodeAny(scope, encoded)
assertTrue(decodedObj.isLyngException())
assertTrue(decodedObj.isInstanceOf("IllegalArgumentException"))
assertIs<ObjException>(decodedObj)
val message = decodedObj.getLyngExceptionMessage(scope)
assertEquals("bad arg", message)
}
}
@@ -4558,6 +4558,7 @@ class ScriptTest {
val x = try { throw IllegalAccessException("test1") } catch { it }
assertEquals("test1", x.message)
assert( x is IllegalAccessException)
assert( x is Exception )
assertThrows(IllegalAccessException) { throw IllegalAccessException("test2") }
class X : Exception("test3")
@@ -4565,6 +4566,7 @@ class ScriptTest {
println(y)
assertEquals("test3", y.message)
assert( y is X)
assert( y is Exception )
""".trimIndent())
}
@@ -4601,6 +4603,17 @@ class ScriptTest {
assert( x is UserException)
val y = try { throw IllegalStateException() } catch { it }
assert( y is IllegalStateException)
// creating exceptions as usual objects:
val z = IllegalArgumentException()
assert( z is Exception )
assert( z is IllegalArgumentException )
class X : Exception
val t = X()
assert( t is X )
assert( t is Exception )
""".trimIndent())
}
@@ -4635,4 +4648,28 @@ class ScriptTest {
assert(caught.message.contains("Expected DerivedEx, got MyEx"))
""".trimIndent())
}
@Test
fun testRaiseAsError() = runTest {
var x = evalNamed( "tc1","""
IllegalArgumentException("test3")
""".trimIndent())
var x1 = try { x.raiseAsExecutionError() } catch(e: ExecutionError) { e }
println(x1.message)
assertTrue { "tc1:1" in x1.message!! }
assertTrue { "test3" in x1.message!! }
// With user exception classes it should be the same at top level:
x = evalNamed("tc2","""
class E: Exception("test4")
E()
""".trimIndent())
x1 = try { x.raiseAsExecutionError() } catch(e: ExecutionError) { e }
println(x1.message)
assertContains(x1.message!!, "test4")
// the reported error message should include proper trace, which must include
// source name, in our case, is is "tc2":
assertContains(x1.message!!, "tc2")
}
}