User exception classes, unified exception class architecture
This commit is contained in:
parent
2ce6d8e482
commit
1d089db9ff
@ -173,6 +173,7 @@ Ready features:
|
|||||||
- [x] ranges, lists, strings, interfaces: Iterable, Iterator, Collection, Array
|
- [x] ranges, lists, strings, interfaces: Iterable, Iterator, Collection, Array
|
||||||
- [x] when(value), if-then-else
|
- [x] when(value), if-then-else
|
||||||
- [x] exception handling: throw, try-catch-finally, exception classes.
|
- [x] exception handling: throw, try-catch-finally, exception classes.
|
||||||
|
- [x] user-defined exception classes
|
||||||
- [x] multiplatform maven publication
|
- [x] multiplatform maven publication
|
||||||
- [x] documentation for the current state
|
- [x] documentation for the current state
|
||||||
- [x] maps, sets and sequences (flows?)
|
- [x] maps, sets and sequences (flows?)
|
||||||
@ -196,6 +197,7 @@ Ready features:
|
|||||||
- [x] late-init vals in classes
|
- [x] late-init vals in classes
|
||||||
- [x] properties with getters and setters
|
- [x] properties with getters and setters
|
||||||
- [x] assign-if-null operator `?=`
|
- [x] assign-if-null operator `?=`
|
||||||
|
- [x] user-defined exception classes
|
||||||
|
|
||||||
All of this is documented in the [language site](https://lynglang.com) and locally [docs/language.md](docs/tutorial.md). the current nightly builds published on the site and in the private maven repository.
|
All of this is documented in the [language site](https://lynglang.com) and locally [docs/language.md](docs/tutorial.md). the current nightly builds published on the site and in the private maven repository.
|
||||||
|
|
||||||
|
|||||||
17
docs/OOP.md
17
docs/OOP.md
@ -720,6 +720,23 @@ Notes and limitations (current version):
|
|||||||
- `name` and `ordinal` are read‑only properties of an entry.
|
- `name` and `ordinal` are read‑only properties of an entry.
|
||||||
- `entries` is a read‑only list owned by the enum type.
|
- `entries` is a read‑only list owned by the enum type.
|
||||||
|
|
||||||
|
## Exception Classes
|
||||||
|
|
||||||
|
You can define your own exception classes by inheriting from the built-in `Exception` class. User-defined exceptions are regular classes and can have their own properties and methods.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class MyError(val code, m) : Exception(m)
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw MyError(500, "Internal Server Error")
|
||||||
|
}
|
||||||
|
catch(e: MyError) {
|
||||||
|
println("Error " + e.code + ": " + e.message)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For more details on error handling, see the [Exceptions Handling Guide](exceptions_handling.md).
|
||||||
|
|
||||||
## fields and visibility
|
## fields and visibility
|
||||||
|
|
||||||
It is possible to add non-constructor fields:
|
It is possible to add non-constructor fields:
|
||||||
|
|||||||
@ -129,7 +129,7 @@ Serializable class that conveys information about the exception. Important membe
|
|||||||
| name | description |
|
| name | description |
|
||||||
|-------------------|--------------------------------------------------------|
|
|-------------------|--------------------------------------------------------|
|
||||||
| message | String message |
|
| message | String message |
|
||||||
| stackTrace | lyng stack trace, list of `StackTraceEntry`, see below |
|
| stackTrace() | lyng stack trace, list of `StackTraceEntry`, see below |
|
||||||
| printStackTrace() | format and print stack trace using println() |
|
| printStackTrace() | format and print stack trace using println() |
|
||||||
|
|
||||||
## StackTraceEntry
|
## StackTraceEntry
|
||||||
@ -150,24 +150,103 @@ class StackTraceEntry(
|
|||||||
|
|
||||||
# Custom error classes
|
# Custom error classes
|
||||||
|
|
||||||
_this functionality is not yet released_
|
You can define your own exception classes by inheriting from the built-in `Exception` class. This allows you to create specific error types for your application logic and catch them specifically.
|
||||||
|
|
||||||
|
## Defining a custom exception
|
||||||
|
|
||||||
|
To define a custom exception, create a class that inherits from `Exception`:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class MyUserException : Exception("something went wrong")
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also pass the message dynamically:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class MyUserException(m) : Exception(m)
|
||||||
|
|
||||||
|
throw MyUserException("custom error message")
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't provide a message to the `Exception` constructor, the class name will be used as the default message:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class SimpleException : Exception
|
||||||
|
|
||||||
|
val e = SimpleException()
|
||||||
|
assertEquals("SimpleException", e.message)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Throwing and catching custom exceptions
|
||||||
|
|
||||||
|
Custom exceptions are thrown using the `throw` keyword and can be caught using `catch` blocks, just like standard exceptions:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class ValidationException(m) : Exception(m)
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw ValidationException("Invalid input")
|
||||||
|
}
|
||||||
|
catch(e: ValidationException) {
|
||||||
|
println("Caught validation error: " + e.message)
|
||||||
|
}
|
||||||
|
catch(e: Exception) {
|
||||||
|
println("Caught other exception: " + e.message)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Since user exceptions are real classes, inheritance works as expected:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class BaseError : Exception
|
||||||
|
class DerivedError : BaseError
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw DerivedError()
|
||||||
|
}
|
||||||
|
catch(e: BaseError) {
|
||||||
|
// This will catch DerivedError as well
|
||||||
|
assert(e is DerivedError)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing extra data
|
||||||
|
|
||||||
|
You can add your own fields to custom exception classes to carry additional information:
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class NetworkException(m, val statusCode) : Exception(m)
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw NetworkException("Not Found", 404)
|
||||||
|
}
|
||||||
|
catch(e: NetworkException) {
|
||||||
|
println("Error " + e.statusCode + ": " + e.message)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# Standard exception classes
|
# Standard exception classes
|
||||||
|
|
||||||
| class | notes |
|
| class | notes |
|
||||||
|----------------------------|-------------------------------------------------------|
|
|----------------------------|-------------------------------------------------------|
|
||||||
| Exception | root of al throwable objects |
|
| Exception | root of all throwable objects |
|
||||||
| NullReferenceException | |
|
| NullReferenceException | |
|
||||||
| AssertionFailedException | |
|
| AssertionFailedException | |
|
||||||
| ClassCastException | |
|
| ClassCastException | |
|
||||||
| IndexOutOfBoundsException | |
|
| IndexOutOfBoundsException | |
|
||||||
| IllegalArgumentException | |
|
| IllegalArgumentException | |
|
||||||
|
| IllegalStateException | |
|
||||||
|
| NoSuchElementException | |
|
||||||
| IllegalAssignmentException | assigning to val, etc. |
|
| IllegalAssignmentException | assigning to val, etc. |
|
||||||
| SymbolNotDefinedException | |
|
| SymbolNotDefinedException | |
|
||||||
| IterationEndException | attempt to read iterator past end, `hasNext == false` |
|
| IterationEndException | attempt to read iterator past end, `hasNext == false` |
|
||||||
| IllegalAccessException | attempt to access private members or like |
|
| IllegalAccessException | attempt to access private members or like |
|
||||||
| UnknownException | unexpected kotlin exception caught |
|
| UnknownException | unexpected internal exception caught |
|
||||||
| | |
|
| NotFoundException | |
|
||||||
|
| IllegalOperationException | |
|
||||||
|
| UnsetException | access to uninitialized late-init val |
|
||||||
|
| NotImplementedException | used by `TODO()` |
|
||||||
|
| SyntaxError | |
|
||||||
|
|
||||||
|
|
||||||
### Symbol resolution errors
|
### Symbol resolution errors
|
||||||
|
|||||||
@ -139,6 +139,20 @@ var name by Observable("initial") { n, old, new ->
|
|||||||
|
|
||||||
The system features a unified interface (`getValue`, `setValue`, `invoke`) and a `bind` hook for initialization-time validation and configuration. See the [Delegation Guide](delegation.md) for more.
|
The system features a unified interface (`getValue`, `setValue`, `invoke`) and a `bind` hook for initialization-time validation and configuration. See the [Delegation Guide](delegation.md) for more.
|
||||||
|
|
||||||
|
### User-Defined Exception Classes
|
||||||
|
You can now create custom exception types by inheriting from the built-in `Exception` class. Custom exceptions are real classes that can have their own fields and methods, and they work seamlessly with `throw` and `try-catch` blocks.
|
||||||
|
|
||||||
|
```lyng
|
||||||
|
class ValidationException(val field, m) : Exception(m)
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw ValidationException("email", "Invalid format")
|
||||||
|
}
|
||||||
|
catch(e: ValidationException) {
|
||||||
|
println("Error in " + e.field + ": " + e.message)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Assign-if-null Operator (`?=`)
|
### Assign-if-null Operator (`?=`)
|
||||||
The new `?=` operator provides a concise way to assign a value only if the target is `null`. It is especially useful for setting default values or lazy initialization.
|
The new `?=` operator provides a concise way to assign a value only if the target is `null`. It is especially useful for setting default values or lazy initialization.
|
||||||
|
|
||||||
|
|||||||
@ -1719,19 +1719,25 @@ class Compiler(
|
|||||||
var errorObject = throwStatement.execute(sc)
|
var errorObject = throwStatement.execute(sc)
|
||||||
// Rebind error scope to the throw-site position so ScriptError.pos is accurate
|
// Rebind error scope to the throw-site position so ScriptError.pos is accurate
|
||||||
val throwScope = sc.createChildScope(pos = start)
|
val throwScope = sc.createChildScope(pos = start)
|
||||||
errorObject = when (errorObject) {
|
if (errorObject is ObjString) {
|
||||||
is ObjString -> ObjException(throwScope, errorObject.value)
|
errorObject = ObjException(throwScope, errorObject.value)
|
||||||
is ObjException -> ObjException(
|
}
|
||||||
|
if (!errorObject.isInstanceOf(ObjException.Root)) {
|
||||||
|
throwScope.raiseError("this is not an exception object: $errorObject")
|
||||||
|
}
|
||||||
|
if (errorObject is ObjException) {
|
||||||
|
errorObject = ObjException(
|
||||||
errorObject.exceptionClass,
|
errorObject.exceptionClass,
|
||||||
throwScope,
|
throwScope,
|
||||||
errorObject.message,
|
errorObject.message,
|
||||||
errorObject.extraData,
|
errorObject.extraData,
|
||||||
errorObject.useStackTrace
|
errorObject.useStackTrace
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> throwScope.raiseError("this is not an exception object: $errorObject")
|
|
||||||
}
|
|
||||||
throwScope.raiseError(errorObject)
|
throwScope.raiseError(errorObject)
|
||||||
|
} else {
|
||||||
|
val msg = errorObject.invokeInstanceMethod(sc, "message").toString(sc).value
|
||||||
|
throwScope.raiseError(errorObject, start, msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1814,25 +1820,25 @@ class Compiler(
|
|||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// convert to appropriate exception
|
// convert to appropriate exception
|
||||||
val objException = when (e) {
|
val caughtObj = when (e) {
|
||||||
is ExecutionError -> e.errorObject
|
is ExecutionError -> e.errorObject
|
||||||
else -> ObjUnknownException(this, e.message ?: e.toString())
|
else -> ObjUnknownException(this, e.message ?: e.toString())
|
||||||
}
|
}
|
||||||
// let's see if we should catch it:
|
// let's see if we should catch it:
|
||||||
var isCaught = false
|
var isCaught = false
|
||||||
for (cdata in catches) {
|
for (cdata in catches) {
|
||||||
var exceptionObject: ObjException? = null
|
var match: Obj? = null
|
||||||
for (exceptionClassName in cdata.classNames) {
|
for (exceptionClassName in cdata.classNames) {
|
||||||
val exObj = ObjException.getErrorClass(exceptionClassName)
|
val exObj = this[exceptionClassName]?.value as? ObjClass
|
||||||
?: raiseSymbolNotFound("error clas not exists: $exceptionClassName")
|
?: raiseSymbolNotFound("error class does not exist or is not a class: $exceptionClassName")
|
||||||
if (objException.isInstanceOf(exObj)) {
|
if (caughtObj.isInstanceOf(exObj)) {
|
||||||
exceptionObject = objException
|
match = caughtObj
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (exceptionObject != null) {
|
if (match != null) {
|
||||||
val catchContext = this.createChildScope(pos = cdata.catchVar.pos)
|
val catchContext = this.createChildScope(pos = cdata.catchVar.pos)
|
||||||
catchContext.addItem(cdata.catchVar.value, false, objException)
|
catchContext.addItem(cdata.catchVar.value, false, caughtObj)
|
||||||
result = cdata.block.execute(catchContext)
|
result = cdata.block.execute(catchContext)
|
||||||
isCaught = true
|
isCaught = true
|
||||||
break
|
break
|
||||||
|
|||||||
@ -277,16 +277,22 @@ open class Scope(
|
|||||||
raiseError(ObjSymbolNotDefinedException(this, "symbol is not defined: $name"))
|
raiseError(ObjSymbolNotDefinedException(this, "symbol is not defined: $name"))
|
||||||
|
|
||||||
fun raiseError(message: String): Nothing {
|
fun raiseError(message: String): Nothing {
|
||||||
throw ExecutionError(ObjException(this, message))
|
val ex = ObjException(this, message)
|
||||||
|
throw ExecutionError(ex, pos, ex.message.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun raiseError(obj: ObjException): Nothing {
|
fun raiseError(obj: ObjException): Nothing {
|
||||||
throw ExecutionError(obj)
|
throw ExecutionError(obj, obj.scope.pos, obj.message.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun raiseError(obj: Obj, pos: Pos, message: String): Nothing {
|
||||||
|
throw ExecutionError(obj, pos, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun raiseNotFound(message: String = "not found"): Nothing {
|
fun raiseNotFound(message: String = "not found"): Nothing {
|
||||||
throw ExecutionError(ObjNotFoundException(this, message))
|
val ex = ObjNotFoundException(this, message)
|
||||||
|
throw ExecutionError(ex, ex.scope.pos, ex.message.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Obj> requiredArg(index: Int): T {
|
inline fun <reified T : Obj> requiredArg(index: Int): T {
|
||||||
|
|||||||
@ -248,10 +248,9 @@ class Script(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
expectedClass?.let {
|
expectedClass?.let {
|
||||||
if (result !is ObjException)
|
if (!result.isInstanceOf(it)) {
|
||||||
raiseError("Expected $expectedClass, got non-lyng exception $result")
|
val actual = if (result is ObjException) result.exceptionClass else result.objClass
|
||||||
if (result.exceptionClass != expectedClass) {
|
raiseError("Expected $it, got $actual")
|
||||||
raiseError("Expected $expectedClass, got ${result.exceptionClass}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
|
|||||||
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import net.sergeych.lyng.obj.ObjException
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
open class ScriptError(val pos: Pos, val errorMessage: String, cause: Throwable? = null) : Exception(
|
open class ScriptError(val pos: Pos, val errorMessage: String, cause: Throwable? = null) : Exception(
|
||||||
"""
|
"""
|
||||||
@ -33,6 +33,6 @@ open class ScriptError(val pos: Pos, val errorMessage: String, cause: Throwable?
|
|||||||
|
|
||||||
class ScriptFlowIsNoMoreCollected: Exception()
|
class ScriptFlowIsNoMoreCollected: Exception()
|
||||||
|
|
||||||
class ExecutionError(val errorObject: ObjException) : ScriptError(errorObject.scope.pos, errorObject.message.value)
|
class ExecutionError(val errorObject: Obj, pos: Pos, message: String) : ScriptError(pos, message)
|
||||||
|
|
||||||
class ImportException(pos: Pos, message: String) : ScriptError(pos, message)
|
class ImportException(pos: Pos, message: String) : ScriptError(pos, message)
|
||||||
@ -41,7 +41,7 @@ open class ObjException(
|
|||||||
val exceptionClass: ExceptionClass,
|
val exceptionClass: ExceptionClass,
|
||||||
val scope: Scope,
|
val scope: Scope,
|
||||||
val message: ObjString,
|
val message: ObjString,
|
||||||
@Suppress("unused") val extraData: Obj = ObjNull,
|
val extraData: Obj = ObjNull,
|
||||||
val useStackTrace: ObjList? = null
|
val useStackTrace: ObjList? = null
|
||||||
) : Obj() {
|
) : Obj() {
|
||||||
constructor(name: String, scope: Scope, message: String) : this(
|
constructor(name: String, scope: Scope, message: String) : this(
|
||||||
@ -54,37 +54,14 @@ open class ObjException(
|
|||||||
|
|
||||||
suspend fun getStackTrace(): ObjList {
|
suspend fun getStackTrace(): ObjList {
|
||||||
return cachedStackTrace.get {
|
return cachedStackTrace.get {
|
||||||
val result = ObjList()
|
captureStackTrace(scope)
|
||||||
val maybeCls = scope.get("StackTraceEntry")?.value as? ObjClass
|
|
||||||
var s: Scope? = scope
|
|
||||||
var lastPos: Pos? = null
|
|
||||||
while (s != null) {
|
|
||||||
val pos = s.pos
|
|
||||||
if (pos != lastPos && !pos.currentLine.isEmpty()) {
|
|
||||||
if (maybeCls != null) {
|
|
||||||
result.list += maybeCls.callWithArgs(
|
|
||||||
scope,
|
|
||||||
pos.source.objSourceName,
|
|
||||||
ObjInt(pos.line.toLong()),
|
|
||||||
ObjInt(pos.column.toLong()),
|
|
||||||
ObjString(pos.currentLine)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Fallback textual entry if StackTraceEntry class is not available in this scope
|
|
||||||
result.list += ObjString("${pos.source.objSourceName}:${pos.line}:${pos.column}: ${pos.currentLine}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s = s.parent
|
|
||||||
lastPos = pos
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(scope: Scope, message: String) : this(Root, scope, ObjString(message))
|
constructor(scope: Scope, message: String) : this(Root, scope, ObjString(message))
|
||||||
|
|
||||||
fun raise(): Nothing {
|
fun raise(): Nothing {
|
||||||
throw ExecutionError(this)
|
throw ExecutionError(this, scope.pos, message.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val objClass: ObjClass = exceptionClass
|
override val objClass: ObjClass = exceptionClass
|
||||||
@ -116,7 +93,41 @@ open class ObjException(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
suspend fun captureStackTrace(scope: Scope): ObjList {
|
||||||
|
val result = ObjList()
|
||||||
|
val maybeCls = scope.get("StackTraceEntry")?.value as? ObjClass
|
||||||
|
var s: Scope? = scope
|
||||||
|
var lastPos: Pos? = null
|
||||||
|
while (s != null) {
|
||||||
|
val pos = s.pos
|
||||||
|
if (pos != lastPos && !pos.currentLine.isEmpty()) {
|
||||||
|
if (maybeCls != null) {
|
||||||
|
result.list += maybeCls.callWithArgs(
|
||||||
|
scope,
|
||||||
|
pos.source.objSourceName,
|
||||||
|
ObjInt(pos.line.toLong()),
|
||||||
|
ObjInt(pos.column.toLong()),
|
||||||
|
ObjString(pos.currentLine)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Fallback textual entry if StackTraceEntry class is not available in this scope
|
||||||
|
result.list += ObjString("${pos.source.objSourceName}:${pos.line}:${pos.column}: ${pos.currentLine}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = s.parent
|
||||||
|
lastPos = pos
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
|
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
|
||||||
|
init {
|
||||||
|
constructorMeta = ArgsDeclaration(
|
||||||
|
listOf(ArgsDeclaration.Item("message", defaultValue = statement { ObjString(name) })),
|
||||||
|
Token.Type.RPAREN
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun callOn(scope: Scope): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
val message = scope.args.getOrNull(0)?.toString(scope) ?: ObjString(name)
|
val message = scope.args.getOrNull(0)?.toString(scope) ?: ObjString(name)
|
||||||
return ObjException(this, scope, message)
|
return ObjException(this, scope, message)
|
||||||
@ -148,22 +159,77 @@ open class ObjException(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val Root = ExceptionClass("Exception").apply {
|
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 stack = captureStackTrace(this)
|
||||||
|
(thisObj as ObjInstance).instanceScope.addItem("Exception::stackTrace", false, stack)
|
||||||
|
}
|
||||||
|
ObjVoid
|
||||||
|
})
|
||||||
addConstDoc(
|
addConstDoc(
|
||||||
name = "message",
|
name = "message",
|
||||||
value = statement {
|
value = statement {
|
||||||
(thisObj as ObjException).message.toObj()
|
when (val t = thisObj) {
|
||||||
|
is ObjException -> t.message
|
||||||
|
is ObjInstance -> t.instanceScope.get("Exception::message")?.value ?: ObjNull
|
||||||
|
else -> ObjNull
|
||||||
|
}
|
||||||
},
|
},
|
||||||
doc = "Human‑readable error message.",
|
doc = "Human‑readable error message.",
|
||||||
type = type("lyng.String"),
|
type = type("lyng.String"),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
)
|
)
|
||||||
|
addConstDoc(
|
||||||
|
name = "extraData",
|
||||||
|
value = statement {
|
||||||
|
when (val t = thisObj) {
|
||||||
|
is ObjException -> t.extraData
|
||||||
|
else -> ObjNull
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doc = "Extra data associated with the exception.",
|
||||||
|
type = type("lyng.Any", nullable = true),
|
||||||
|
moduleName = "lyng.stdlib"
|
||||||
|
)
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
name = "stackTrace",
|
name = "stackTrace",
|
||||||
doc = "Stack trace captured at throw site as a list of `StackTraceEntry`.",
|
doc = "Stack trace captured at throw site as a list of `StackTraceEntry`.",
|
||||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))),
|
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))),
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
(thisObj as ObjException).getStackTrace()
|
when (val t = thisObj) {
|
||||||
|
is ObjException -> t.getStackTrace()
|
||||||
|
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||||
|
else -> ObjList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addFnDoc(
|
||||||
|
name = "toString",
|
||||||
|
doc = "Human‑readable string representation of the error.",
|
||||||
|
returns = type("lyng.String"),
|
||||||
|
moduleName = "lyng.stdlib"
|
||||||
|
) {
|
||||||
|
val msg = when (val t = thisObj) {
|
||||||
|
is ObjException -> t.message.value
|
||||||
|
is ObjInstance -> (t.instanceScope.get("Exception::message")?.value as? ObjString)?.value
|
||||||
|
?: t.objClass.className
|
||||||
|
|
||||||
|
else -> t.objClass.className
|
||||||
|
}
|
||||||
|
val stack = when (val t = thisObj) {
|
||||||
|
is ObjException -> t.getStackTrace()
|
||||||
|
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||||
|
else -> ObjList()
|
||||||
|
}
|
||||||
|
val at = stack.list.firstOrNull()?.toString(this) ?: ObjString("(unknown)")
|
||||||
|
ObjString("${thisObj.objClass.className}: $msg at $at")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4552,22 +4552,22 @@ class ScriptTest {
|
|||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
@Test
|
||||||
// fun testUserClassExceptions() = runTest {
|
fun testUserClassExceptions() = runTest {
|
||||||
// eval("""
|
eval("""
|
||||||
// val x = try { throw IllegalAccessException("test1") } catch { it }
|
val x = try { throw IllegalAccessException("test1") } catch { it }
|
||||||
// assertEquals("test1", x.message)
|
assertEquals("test1", x.message)
|
||||||
// assert( x is IllegalAccessException)
|
assert( x is IllegalAccessException)
|
||||||
// assertThrows(IllegalAccessException) { throw IllegalAccessException("test2") }
|
assertThrows(IllegalAccessException) { throw IllegalAccessException("test2") }
|
||||||
//
|
|
||||||
// class X : Exception("test3")
|
class X : Exception("test3")
|
||||||
// val y = try { throw X() } catch { it }
|
val y = try { throw X() } catch { it }
|
||||||
// println(y)
|
println(y)
|
||||||
// assertEquals("test3", y.message)
|
assertEquals("test3", y.message)
|
||||||
// assert( y is X)
|
assert( y is X)
|
||||||
//
|
|
||||||
// """.trimIndent())
|
""".trimIndent())
|
||||||
// }
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTodo() = runTest {
|
fun testTodo() = runTest {
|
||||||
@ -4592,4 +4592,47 @@ class ScriptTest {
|
|||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testUserExceptionClass() = runTest {
|
||||||
|
eval("""
|
||||||
|
class UserException : Exception("user exception")
|
||||||
|
val x = try { throw UserException() } catch { it }
|
||||||
|
assertEquals("user exception", x.message)
|
||||||
|
assert( x is UserException)
|
||||||
|
val y = try { throw IllegalStateException() } catch { it }
|
||||||
|
assert( y is IllegalStateException)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExceptionToString() = runTest {
|
||||||
|
eval("""
|
||||||
|
class MyEx(m) : Exception(m)
|
||||||
|
val e = MyEx("custom error")
|
||||||
|
val s = e.toString()
|
||||||
|
assert( s.startsWith("MyEx: custom error at ") )
|
||||||
|
|
||||||
|
val e2 = try { throw e } catch { it }
|
||||||
|
assert( e2 === e )
|
||||||
|
assertEquals("custom error", e2.message)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun testAssertThrowsUserException() = runTest {
|
||||||
|
eval("""
|
||||||
|
class MyEx : Exception
|
||||||
|
class DerivedEx : MyEx
|
||||||
|
|
||||||
|
assertThrows(MyEx) { throw MyEx() }
|
||||||
|
assertThrows(Exception) { throw MyEx() }
|
||||||
|
assertThrows(MyEx) { throw DerivedEx() }
|
||||||
|
|
||||||
|
val caught = try {
|
||||||
|
assertThrows(DerivedEx) { throw MyEx() }
|
||||||
|
null
|
||||||
|
} catch { it }
|
||||||
|
assert(caught != null)
|
||||||
|
assert(caught.message.contains("Expected DerivedEx, got MyEx"))
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user