From 514ad96148c046e69ad0339fa2e7de6321f1f76e Mon Sep 17 00:00:00 2001 From: sergeych Date: Mon, 5 Jan 2026 11:14:05 +0100 Subject: [PATCH] AccessException -< IllegalAccessException; added TODO() and NotImplementedException --- docs/Testing.md | 16 +++++++-- .../kotlin/net/sergeych/lyng/Script.kt | 34 +++++++++++++++---- .../kotlin/net/sergeych/lyng/obj/Obj.kt | 8 ++--- .../net/sergeych/lyng/obj/ObjException.kt | 12 ++++--- .../net/sergeych/lyng/obj/ObjInstance.kt | 28 +++++++-------- .../kotlin/net/sergeych/lyng/obj/ObjRef.kt | 14 ++++---- lynglib/src/commonTest/kotlin/OOTest.kt | 6 ++-- lynglib/src/commonTest/kotlin/ScriptTest.kt | 30 +++++++++++++++- lynglib/stdlib/lyng/root.lyng | 1 + 9 files changed, 107 insertions(+), 42 deletions(-) diff --git a/docs/Testing.md b/docs/Testing.md index b344293..34abca8 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -93,8 +93,6 @@ If we want to evaluate the message lazily: In this case, formatting will only occur if the condition is not met. - - ### `check` check(condition, message="check failed") @@ -107,3 +105,17 @@ With lazy message evaluation: In this case, formatting will only occur if the condition is not met. +### TODO + +It is easy to mark some code and make it throw a special exception at cone with: + + TODO() + +or + + TODO("some message") + +It raises an `NotImplementedException` with the given message. You can catch it +as any other exception when necessary. + +Many IDE and editors have built-in support for marking code with TODOs. diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index 3e54926..0fe4726 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.yield import net.sergeych.lyng.Script.Companion.defaultImportManager import net.sergeych.lyng.miniast.addConstDoc +import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addVoidFnDoc import net.sergeych.lyng.miniast.type import net.sergeych.lyng.obj.* @@ -204,18 +205,32 @@ class Script( ) ) } - addFn("assertThrows") { + addFnDoc( + "assertThrows", + doc = """ + Asserts that the provided code block throws an exception, with or without exception: + ```lyng + assertThrows { /* ode */ } + assertThrows(IllegalArgumentException) { /* code */ } + ``` + If an expected exception class is provided, + it checks that the thrown exception is of that class. If no expected class is provided, any exception + will be accepted. + """.trimIndent() + ) { val code: Statement val expectedClass: ObjClass? - when(args.size) { + when (args.size) { 1 -> { code = requiredArg(0) expectedClass = null } + 2 -> { code = requiredArg(1) expectedClass = requiredArg(0) } + else -> raiseIllegalArgument("Expected 1 or 2 arguments, got ${args.size}") } val result = try { @@ -226,10 +241,15 @@ class Script( } catch (_: ScriptError) { ObjNull } - if( result == null ) raiseError(ObjAssertionFailedException(this, "Expected exception but nothing was thrown")) + if (result == null) raiseError( + ObjAssertionFailedException( + this, + "Expected exception but nothing was thrown" + ) + ) expectedClass?.let { - if( result !is ObjException) - raiseError("Expected $expectedClass, got $result") + if (result !is ObjException) + raiseError("Expected $expectedClass, got non-lyng exception $result") if (result.exceptionClass != expectedClass) { raiseError("Expected $expectedClass, got ${result.exceptionClass}") } @@ -255,7 +275,7 @@ class Script( val condition = requiredArg(0) if (!condition.value) { var message = args.list.getOrNull(1) - if( message is Statement ) message = message.execute(this) + if (message is Statement) message = message.execute(this) raiseIllegalArgument(message?.toString() ?: "requirement not met") } ObjVoid @@ -264,7 +284,7 @@ class Script( val condition = requiredArg(0) if (!condition.value) { var message = args.list.getOrNull(1) - if( message is Statement ) message = message.execute(this) + if (message is Statement) message = message.execute(this) raiseIllegalState(message?.toString() ?: "check failed") } ObjVoid diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt index a209ac6..482baec 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt @@ -93,7 +93,7 @@ open class Obj { val decl = rec.declaringClass ?: cls val caller = scope.currentClassCtx if (!canAccessMember(rec.visibility, decl, caller)) - scope.raiseError(ObjAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) + scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) val saved = scope.currentClassCtx scope.currentClassCtx = decl try { @@ -117,7 +117,7 @@ open class Obj { val decl = rec.declaringClass ?: cls val caller = scope.currentClassCtx if (!canAccessMember(rec.visibility, decl, caller)) - scope.raiseError(ObjAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) + scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) val saved = scope.currentClassCtx scope.currentClassCtx = decl try { @@ -387,7 +387,7 @@ open class Obj { val caller = scope.currentClassCtx // Check visibility for non-property members here if they weren't checked before if (!canAccessMember(obj.visibility, decl, caller)) - scope.raiseError(ObjAccessException(scope, "can't access field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})")) + scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})")) return obj } @@ -424,7 +424,7 @@ open class Obj { val decl = field.declaringClass val caller = scope.currentClassCtx if (!canAccessMember(field.effectiveWriteVisibility, decl, caller)) - scope.raiseError(ObjAccessException(scope, "can't assign field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})")) + scope.raiseError(ObjIllegalAccessException(scope, "can't assign field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})")) if (field.value is ObjProperty) { (field.value as ObjProperty).callSetter(scope, this, newValue, decl) } else if (field.isMutable) field.value = newValue else scope.raiseError("can't assign to read-only field: $name") diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt index e3a3f01..065e79a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * 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. @@ -191,11 +191,12 @@ open class ObjException( "IllegalAssignmentException", "SymbolNotDefinedException", "IterationEndException", - "AccessException", + "IllegalAccessException", "UnknownException", "NotFoundException", "IllegalOperationException", "UnsetException", + "NotImplementedException", "SyntaxError" )) { scope.addConst(name, getOrCreateExceptionClass(name)) @@ -236,8 +237,8 @@ class ObjSymbolNotDefinedException(scope: Scope, message: String = "symbol is no class ObjIterationFinishedException(scope: Scope) : ObjException("IterationEndException", scope, "iteration finished") -class ObjAccessException(scope: Scope, message: String = "access not allowed error") : - ObjException("AccessException", scope, message) +class ObjIllegalAccessException(scope: Scope, message: String = "access not allowed error") : + ObjException("IllegalAccessException", scope, message) class ObjUnknownException(scope: Scope, message: String = "access not allowed error") : ObjException("UnknownException", scope, message) @@ -250,3 +251,6 @@ class ObjNotFoundException(scope: Scope, message: String = "not found") : class ObjUnsetException(scope: Scope, message: String = "property is unset (not initialized)") : ObjException("UnsetException", scope, message) + +class ObjNotImplementedException(scope: Scope, message: String = "not implemented") : + ObjException("NotImplementedException", scope, message) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt index 2504fbd..eefc376 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt @@ -40,7 +40,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { val caller = scope.currentClassCtx if (!canAccessMember(rec.visibility, decl, caller)) scope.raiseError( - ObjAccessException( + ObjIllegalAccessException( scope, "can't access field $name (declared in ${decl?.className ?: "?"})" ) @@ -81,7 +81,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { val caller = scope.currentClassCtx if (!canAccessMember(rec.visibility, declaring, caller)) scope.raiseError( - ObjAccessException( + ObjIllegalAccessException( scope, "can't access field $name (declared in ${declaring?.className ?: "?"})" ) @@ -104,7 +104,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { if (scope.thisObj !== this || scope.currentClassCtx == null) { val caller = scope.currentClassCtx if (!canAccessMember(f.effectiveWriteVisibility, decl, caller)) - ObjAccessException( + ObjIllegalAccessException( scope, "can't assign to field $name (declared in ${decl?.className ?: "?"})" ).raise() @@ -139,7 +139,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { if (scope.thisObj !== this || scope.currentClassCtx == null) { val caller = scope.currentClassCtx if (!canAccessMember(rec.effectiveWriteVisibility, declaring, caller)) - ObjAccessException( + ObjIllegalAccessException( scope, "can't assign to field $name (declared in ${declaring?.className ?: "?"})" ).raise() @@ -168,7 +168,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null if (!canAccessMember(rec.visibility, decl, caller)) scope.raiseError( - ObjAccessException( + ObjIllegalAccessException( scope, "can't invoke method $name (declared in ${decl?.className ?: "?"})" ) @@ -189,7 +189,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null if (!canAccessMember(rec.visibility, decl, caller)) scope.raiseError( - ObjAccessException( + ObjIllegalAccessException( scope, "can't invoke method $name (declared in ${decl?.className ?: "?"})" ) @@ -319,7 +319,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla val decl = rec.declaringClass ?: startClass val caller = scope.currentClassCtx if (!canAccessMember(rec.visibility, decl, caller)) - scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${decl.className})")) + scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})")) return rec } // Then try instance locals (unmangled) only if startClass is the dynamic class itself @@ -329,7 +329,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla val caller = scope.currentClassCtx if (!canAccessMember(rec.visibility, decl, caller)) scope.raiseError( - ObjAccessException( + ObjIllegalAccessException( scope, "can't access field $name (declared in ${decl?.className ?: "?"})" ) @@ -342,7 +342,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla val decl = r.declaringClass ?: startClass val caller = scope.currentClassCtx if (!canAccessMember(r.visibility, decl, caller)) - scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${decl.className})")) + scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})")) return when (val value = r.value) { is net.sergeych.lyng.Statement -> ObjRecord( value.execute( @@ -364,7 +364,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla val decl = f.declaringClass ?: startClass val caller = scope.currentClassCtx if (!canAccessMember(f.effectiveWriteVisibility, decl, caller)) - ObjAccessException( + ObjIllegalAccessException( scope, "can't assign to field $name (declared in ${decl.className})" ).raise() @@ -378,7 +378,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla val decl = f.declaringClass ?: instance.objClass.findDeclaringClassOf(name) val caller = scope.currentClassCtx if (!canAccessMember(f.effectiveWriteVisibility, decl, caller)) - ObjAccessException( + ObjIllegalAccessException( scope, "can't assign to field $name (declared in ${decl?.className ?: "?"})" ).raise() @@ -391,7 +391,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla val decl = r.declaringClass ?: startClass val caller = scope.currentClassCtx if (!canAccessMember(r.effectiveWriteVisibility, decl, caller)) - ObjAccessException(scope, "can't assign to field $name (declared in ${decl.className})").raise() + ObjIllegalAccessException(scope, "can't assign to field $name (declared in ${decl.className})").raise() if (!r.isMutable) scope.raiseError("can't assign to read-only field: $name") if (r.value.assign(scope, newValue) == null) r.value = newValue } @@ -407,7 +407,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla val decl = rec.declaringClass ?: startClass val caller = scope.currentClassCtx if (!canAccessMember(rec.visibility, decl, caller)) - scope.raiseError(ObjAccessException(scope, "can't invoke method $name (declared in ${decl.className})")) + scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})")) val saved = instance.instanceScope.currentClassCtx instance.instanceScope.currentClassCtx = decl try { @@ -423,7 +423,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla val caller = scope.currentClassCtx if (!canAccessMember(rec.visibility, decl, caller)) scope.raiseError( - ObjAccessException( + ObjIllegalAccessException( scope, "can't invoke method $name (declared in ${decl?.className ?: "?"})" ) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt index 6158696..89467c7 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * 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. @@ -595,7 +595,7 @@ class FieldRef( val scope0 = (obj as ObjClass).classScope!! val r0 = scope0.getSlotRecord(capturedIdx) if (!r0.visibility.isPublic) - sc.raiseError(ObjAccessException(sc, "can't access non-public field $name")) + sc.raiseError(ObjIllegalAccessException(sc, "can't access non-public field $name")) r0 } } else { @@ -631,7 +631,7 @@ class FieldRef( // visibility/mutability checks if (!rec.isMutable) scope.raiseError(ObjIllegalAssignmentException(scope, "can't reassign val $name")) if (!rec.visibility.isPublic) - scope.raiseError(ObjAccessException(scope, "can't access non-public field $name")) + scope.raiseError(ObjIllegalAccessException(scope, "can't access non-public field $name")) if (rec.value.assign(scope, newValue) == null) rec.value = newValue return } @@ -1195,7 +1195,7 @@ class MethodCallRef( mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> val inst = obj as ObjInstance if (!visibility.isPublic && !canAccessMember(visibility, hierarchyMember.declaringClass ?: inst.objClass, sc.currentClassCtx)) - sc.raiseError(ObjAccessException(sc, "can't invoke non-public method $name")) + sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name")) callable.invoke(inst.instanceScope, inst, a) } } else { @@ -1467,7 +1467,7 @@ class BoundLocalVarRef( scope.pos = atPos val rec = scope.getSlotRecord(slot) if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) - scope.raiseError(ObjAccessException(scope, "private field access")) + scope.raiseError(ObjIllegalAccessException(scope, "private field access")) return rec } @@ -1475,7 +1475,7 @@ class BoundLocalVarRef( scope.pos = atPos val rec = scope.getSlotRecord(slot) if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) - scope.raiseError(ObjAccessException(scope, "private field access")) + scope.raiseError(ObjIllegalAccessException(scope, "private field access")) return rec.value } @@ -1483,7 +1483,7 @@ class BoundLocalVarRef( scope.pos = atPos val rec = scope.getSlotRecord(slot) if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) - scope.raiseError(ObjAccessException(scope, "private field access")) + scope.raiseError(ObjIllegalAccessException(scope, "private field access")) if (!rec.isMutable) scope.raiseError("Cannot assign to immutable value") rec.value = newValue } diff --git a/lynglib/src/commonTest/kotlin/OOTest.kt b/lynglib/src/commonTest/kotlin/OOTest.kt index ec38098..b9ed415 100644 --- a/lynglib/src/commonTest/kotlin/OOTest.kt +++ b/lynglib/src/commonTest/kotlin/OOTest.kt @@ -488,7 +488,7 @@ class OOTest { fun setValue(newValue) { y = newValue } } assertEquals(100, A().y) - assertThrows(AccessException) { A().y = 200 } + assertThrows(IllegalAccessException) { A().y = 200 } val a = A() a.setValue(200) assertEquals(200, a.y) @@ -502,7 +502,7 @@ class OOTest { } val c = C(10) assertEquals(10, c.y) - assertThrows(AccessException) { c.y = 20 } + assertThrows(IllegalAccessException) { c.y = 20 } c.setBValue(30) assertEquals(30, c.y) @@ -515,7 +515,7 @@ class OOTest { } val d = D() assertEquals(0, d.y) - assertThrows(AccessException) { d.y = 10 } + assertThrows(IllegalAccessException) { d.y = 10 } d.setY(20) assertEquals(20, d.y) """ diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 089ac03..63b43d2 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * 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. @@ -4532,4 +4532,32 @@ class ScriptTest { """.trimIndent()) } +// @Test +// fun testUserClassExceptions() = runTest { +// eval(""" +// val x = try { throw IllegalAccessException("test1") } catch { it } +// assertEquals("test1", x.message) +// assert( x is IllegalAccessException) +// assertThrows(IllegalAccessException) { throw IllegalAccessException("test2") } +// +// class X : Exception("test3") +// val y = try { throw X() } catch { it } +// println(y) +// assertEquals("test3", y.message) +// assert( y is X) +// +// """.trimIndent()) +// } + + @Test + fun testTodo() = runTest { + eval(""" + assertThrows(NotImplementedException) { + TODO() + } + val x = try { TODO("check me") } catch { it } + assertEquals("check me", x.message) + """.trimIndent()) + } + } diff --git a/lynglib/stdlib/lyng/root.lyng b/lynglib/stdlib/lyng/root.lyng index 5289520..0e3bafb 100644 --- a/lynglib/stdlib/lyng/root.lyng +++ b/lynglib/stdlib/lyng/root.lyng @@ -276,3 +276,4 @@ fun Exception.printStackTrace() { /* Compile this string into a regular expression. */ val String.re get() = Regex(this) +fun TODO(message=null) = throw NotImplementedException(message ?: "not implemented")