diff --git a/gs/builtin/panic.ts b/gs/builtin/panic.ts index 63e1db187..a9e37ec42 100644 --- a/gs/builtin/panic.ts +++ b/gs/builtin/panic.ts @@ -51,6 +51,9 @@ export class RuntimeError extends Error { Error(): string { return this.message } + + // RuntimeError marks the value as a runtime.Error, as in Go. + RuntimeError(): void {} } /** diff --git a/gs/runtime/runtime.ts b/gs/runtime/runtime.ts index dbac5028a..bd199d5af 100644 --- a/gs/runtime/runtime.ts +++ b/gs/runtime/runtime.ts @@ -407,6 +407,17 @@ export interface Error { Error(): string | PromiseLike } +// Error is registered with the runtime type system, so type assertions to +// runtime.Error see its methods. +$.registerInterfaceType('runtime.Error', null, [ + { + name: 'Error', + args: [], + returns: [{ type: { kind: $.TypeKind.Basic, name: 'string' } }], + }, + { name: 'RuntimeError', args: [], returns: [] }, +]) + // TypeAssertionError represents a failed type assertion. export class TypeAssertionError implements Error { constructor( @@ -424,6 +435,31 @@ export class TypeAssertionError implements Error { } } +// PanicNilError is the value of a panic called with a nil argument, as Go +// 1.21 and later panic with. +export class PanicNilError { + constructor(_init?: Partial<{}>) {} + + public clone(): PanicNilError { + return new PanicNilError() + } + + public Error(): string { + return 'runtime error: panic called with nil argument' + } + + public RuntimeError(): void {} + + // PanicNilError is registered with the runtime type system; the compiler + // gives its method signatures, for reflection. + static __typeInfo = $.registerStructType( + 'runtime.PanicNilError', + new PanicNilError(), + [], + PanicNilError, + ) +} + // PanicError represents a panic. export class PanicError implements Error { constructor(public readonly value: any) {} diff --git a/gs/runtime/stubs.gs.ts b/gs/runtime/stubs.gs.ts index c66a1541c..fdc253234 100644 --- a/gs/runtime/stubs.gs.ts +++ b/gs/runtime/stubs.gs.ts @@ -46,14 +46,6 @@ export function MutexProfile(..._args: any[]): any { return $.notImplemented("runtime.MutexProfile") } -export class PanicNilError { - [field: string]: any - - constructor(..._args: any[]) { - $.notImplemented("runtime.PanicNilError") - } -} - export class Pinner { [field: string]: any diff --git a/tests/tests/package_import_runtime/expected.log b/tests/tests/package_import_runtime/expected.log index 953f4a77c..f41e3b757 100644 --- a/tests/tests/package_import_runtime/expected.log +++ b/tests/tests/package_import_runtime/expected.log @@ -21,3 +21,5 @@ Cleanup stopped Finalizer ignored locked goroutine ran main unlocked +PanicNilError: runtime error: panic called with nil argument true +recovered runtime.Error: true diff --git a/tests/tests/package_import_runtime/package_import_runtime.go b/tests/tests/package_import_runtime/package_import_runtime.go index cb2154349..1743bca3b 100644 --- a/tests/tests/package_import_runtime/package_import_runtime.go +++ b/tests/tests/package_import_runtime/package_import_runtime.go @@ -58,6 +58,7 @@ func main() { println("Finalizer ignored") lockOSThread() + panicNilError() } // callers reports frames of its own stack and names a function value. @@ -124,3 +125,18 @@ func lockOSThread() { runtime.UnlockOSThread() println("main unlocked") } + +// panicNilError checks the value Go panics with for panic(nil). +func panicNilError() { + var err error = &runtime.PanicNilError{} + _, isRuntime := err.(runtime.Error) + println("PanicNilError:", err.Error(), isRuntime) + + defer func() { + _, ok := recover().(runtime.Error) + println("recovered runtime.Error:", ok) + }() + var s []int + i := 5 + _ = s[i] +} diff --git a/tests/tests/package_import_runtime/package_import_runtime.gs.ts b/tests/tests/package_import_runtime/package_import_runtime.gs.ts index 94ebe106e..7877bc6ee 100644 --- a/tests/tests/package_import_runtime/package_import_runtime.gs.ts +++ b/tests/tests/package_import_runtime/package_import_runtime.gs.ts @@ -125,6 +125,7 @@ export async function main(): globalThis.Promise { await $.println("Finalizer ignored") await lockOSThread() + await panicNilError() } export async function callers(): globalThis.Promise { @@ -178,6 +179,29 @@ export async function lockOSThread(): globalThis.Promise { await $.println("main unlocked") } +export async function panicNilError(): globalThis.Promise { + const __defer = new $.AsyncDisposableStack() + try { + let err: $.GoError = $.interfaceValue<$.GoError>(new runtime.PanicNilError(), "*runtime.PanicNilError", __goscriptStatic2()) + let [, isRuntime] = $.typeAssertTuple(err, "runtime.Error") + await $.println("PanicNilError:", (__goscriptAwait = (err !== null && (err as any).__isVarRef !== true && ((err as any).__goValue === undefined || (err as any).__goType?.charCodeAt(0) !== 42) ? (err as Exclude<$.GoError, null>) : $.pointerValue>(err)).Error(), __goscriptAwait instanceof Promise ? await __goscriptAwait : __goscriptAwait), isRuntime) + + __defer.defer(async () => { await (async () => { + let [, ok] = $.typeAssertTuple($.recover(), "runtime.Error") + await $.println("recovered runtime.Error:", ok) + })() }) + let s: $.Slice = null! as $.Slice + let i = 5 + $.arrayIndex(s!, i) + await __defer.dispose() + } catch (e) { + await __defer.disposePanic(e) + if (!$.recovered(e)) { + throw e + } + } +} + var __goscriptStatic0$value: any function __goscriptStatic0(): any { return __goscriptStatic0$value ??= ({[$.genericTypeArgsMarker]: $.genericTypeArgsBrand, E: { type: /* @__PURE__ */ $.basicType("int"), zero: () => 0 }}) @@ -188,6 +212,13 @@ function __goscriptStatic1(): any { return __goscriptStatic1$value ??= ([{Get: (receiver: any) => $.pointerValue(receiver).Get(__goscriptStatic0())}, [$.methodSignature("Get", [], [/* @__PURE__ */ $.basicType("int")], true, false)]]) } +var __goscriptStatic2$value: any +function __goscriptStatic2(): any { + return __goscriptStatic2$value ??= ($.overrideTypeMethods(/* @__PURE__ */ $.pointerType("runtime.PanicNilError"), "runtime.PanicNilError", () => [$.methodSignature("Error", [], [/* @__PURE__ */ $.basicType("string")], true), $.methodSignature("RuntimeError", [], [], true)])) +} + +var __goscriptAwait: any + __goscriptInit0() if ($.isMainScript(import.meta)) {