goscript 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cmd/goscript/cmd-compile.go +7 -0
- package/cmd/goscript/cmd_compile_test.go +83 -0
- package/compiler/compile-request.go +3 -0
- package/compiler/compiler-cache.go +828 -0
- package/compiler/compiler-cache_test.go +705 -0
- package/compiler/config.go +2 -0
- package/compiler/index.test.ts +26 -1
- package/compiler/index.ts +5 -0
- package/compiler/lowered-program.go +31 -20
- package/compiler/lowering.go +349 -93
- package/compiler/lowering_bench_test.go +1 -0
- package/compiler/override-facts.go +309 -8
- package/compiler/override-parity-verifier.go +45 -1
- package/compiler/override-parity-verifier_test.go +100 -0
- package/compiler/override-registry_test.go +1 -0
- package/compiler/package-graph.go +40 -12
- package/compiler/package-graph_test.go +29 -0
- package/compiler/runtime-contract.go +8 -0
- package/compiler/service.go +98 -11
- package/compiler/skeleton_test.go +110 -14
- package/compiler/typescript-emitter.go +120 -23
- package/dist/compiler/index.d.ts +2 -0
- package/dist/compiler/index.js +3 -0
- package/dist/compiler/index.js.map +1 -1
- package/dist/gs/builtin/builtin.d.ts +24 -33
- package/dist/gs/builtin/builtin.js +54 -61
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/hostio.d.ts +1 -0
- package/dist/gs/builtin/hostio.js +1 -1
- package/dist/gs/builtin/hostio.js.map +1 -1
- package/dist/gs/builtin/index.d.ts +1 -0
- package/dist/gs/builtin/index.js +1 -0
- package/dist/gs/builtin/index.js.map +1 -1
- package/dist/gs/builtin/panic.d.ts +18 -0
- package/dist/gs/builtin/panic.js +98 -0
- package/dist/gs/builtin/panic.js.map +1 -0
- package/dist/gs/builtin/slice.d.ts +10 -0
- package/dist/gs/builtin/slice.js +110 -53
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.js +15 -3
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/builtin/varRef.d.ts +1 -1
- package/dist/gs/builtin/varRef.js +3 -2
- package/dist/gs/builtin/varRef.js.map +1 -1
- package/dist/gs/bytes/bytes.gs.js +51 -38
- package/dist/gs/bytes/bytes.gs.js.map +1 -1
- package/dist/gs/bytes/reader.gs.d.ts +1 -1
- package/dist/gs/bytes/reader.gs.js +6 -7
- package/dist/gs/bytes/reader.gs.js.map +1 -1
- package/dist/gs/cmp/index.d.ts +1 -1
- package/dist/gs/cmp/index.js +43 -10
- package/dist/gs/cmp/index.js.map +1 -1
- package/dist/gs/context/context.d.ts +2 -2
- package/dist/gs/context/context.js +1 -1
- package/dist/gs/context/context.js.map +1 -1
- package/dist/gs/embed/index.js +1 -1
- package/dist/gs/embed/index.js.map +1 -1
- package/dist/gs/encoding/binary/index.js +201 -8
- package/dist/gs/encoding/binary/index.js.map +1 -1
- package/dist/gs/encoding/json/index.d.ts +5 -0
- package/dist/gs/encoding/json/index.js +388 -25
- package/dist/gs/encoding/json/index.js.map +1 -1
- package/dist/gs/errors/errors.js +17 -24
- package/dist/gs/errors/errors.js.map +1 -1
- package/dist/gs/fmt/fmt.js +129 -35
- package/dist/gs/fmt/fmt.js.map +1 -1
- package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1 -1
- package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -1
- package/dist/gs/internal/bytealg/index.js +43 -8
- package/dist/gs/internal/bytealg/index.js.map +1 -1
- package/dist/gs/internal/byteorder/index.d.ts +2 -2
- package/dist/gs/internal/byteorder/index.js +2 -2
- package/dist/gs/internal/byteorder/index.js.map +1 -1
- package/dist/gs/io/fs/format.js +2 -2
- package/dist/gs/io/fs/format.js.map +1 -1
- package/dist/gs/io/fs/fs.d.ts +1 -1
- package/dist/gs/io/fs/fs.js +1 -1
- package/dist/gs/io/fs/fs.js.map +1 -1
- package/dist/gs/io/io.d.ts +21 -21
- package/dist/gs/io/io.js +49 -50
- package/dist/gs/io/io.js.map +1 -1
- package/dist/gs/math/bits/index.js +26 -8
- package/dist/gs/math/bits/index.js.map +1 -1
- package/dist/gs/math/copysign.gs.js +10 -17
- package/dist/gs/math/copysign.gs.js.map +1 -1
- package/dist/gs/math/pow.gs.js +5 -0
- package/dist/gs/math/pow.gs.js.map +1 -1
- package/dist/gs/math/signbit.gs.js +6 -2
- package/dist/gs/math/signbit.gs.js.map +1 -1
- package/dist/gs/mime/index.js +1 -0
- package/dist/gs/mime/index.js.map +1 -1
- package/dist/gs/net/http/index.d.ts +6 -6
- package/dist/gs/net/http/index.js +507 -43
- package/dist/gs/net/http/index.js.map +1 -1
- package/dist/gs/os/stat.gs.d.ts +2 -2
- package/dist/gs/os/types.gs.d.ts +1 -1
- package/dist/gs/os/types.gs.js +1 -1
- package/dist/gs/os/types.gs.js.map +1 -1
- package/dist/gs/os/types_js.gs.d.ts +1 -1
- package/dist/gs/os/types_js.gs.js +7 -7
- package/dist/gs/os/types_js.gs.js.map +1 -1
- package/dist/gs/os/types_unix.gs.d.ts +1 -1
- package/dist/gs/os/types_unix.gs.js +1 -1
- package/dist/gs/os/types_unix.gs.js.map +1 -1
- package/dist/gs/os/zero_copy_posix.gs.d.ts +1 -1
- package/dist/gs/os/zero_copy_posix.gs.js +1 -1
- package/dist/gs/os/zero_copy_posix.gs.js.map +1 -1
- package/dist/gs/path/filepath/match.js +8 -4
- package/dist/gs/path/filepath/match.js.map +1 -1
- package/dist/gs/path/filepath/path.js +216 -42
- package/dist/gs/path/filepath/path.js.map +1 -1
- package/dist/gs/path/match.js +6 -3
- package/dist/gs/path/match.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +5 -4
- package/dist/gs/reflect/type.js +29 -11
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/slices/slices.js +11 -11
- package/dist/gs/slices/slices.js.map +1 -1
- package/dist/gs/strconv/atof.gs.js +156 -43
- package/dist/gs/strconv/atof.gs.js.map +1 -1
- package/dist/gs/strconv/atoi.gs.d.ts +3 -2
- package/dist/gs/strconv/atoi.gs.js +86 -67
- package/dist/gs/strconv/atoi.gs.js.map +1 -1
- package/dist/gs/strconv/ftoa.gs.js +73 -3
- package/dist/gs/strconv/ftoa.gs.js.map +1 -1
- package/dist/gs/strconv/itoa.gs.d.ts +4 -4
- package/dist/gs/strconv/itoa.gs.js +5 -4
- package/dist/gs/strconv/itoa.gs.js.map +1 -1
- package/dist/gs/strconv/quote.gs.d.ts +1 -1
- package/dist/gs/strconv/quote.gs.js +311 -103
- package/dist/gs/strconv/quote.gs.js.map +1 -1
- package/dist/gs/strings/reader.d.ts +1 -1
- package/dist/gs/strings/reader.js +8 -8
- package/dist/gs/strings/reader.js.map +1 -1
- package/dist/gs/strings/strings.js +87 -61
- package/dist/gs/strings/strings.js.map +1 -1
- package/dist/gs/sync/atomic/doc_64.gs.d.ts +14 -14
- package/dist/gs/sync/atomic/doc_64.gs.js +10 -10
- package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
- package/dist/gs/sync/atomic/type.gs.d.ts +22 -22
- package/dist/gs/sync/atomic/type.gs.js +4 -4
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/dist/gs/sync/sync.js +50 -12
- package/dist/gs/sync/sync.js.map +1 -1
- package/dist/gs/syscall/fs.d.ts +6 -6
- package/dist/gs/syscall/fs.js +1 -1
- package/dist/gs/syscall/fs.js.map +1 -1
- package/dist/gs/time/time.d.ts +18 -18
- package/dist/gs/time/time.js +58 -55
- package/dist/gs/time/time.js.map +1 -1
- package/dist/gs/unicode/tables.d.ts +11 -0
- package/dist/gs/unicode/tables.js +635 -0
- package/dist/gs/unicode/tables.js.map +1 -0
- package/dist/gs/unicode/unicode.d.ts +58 -38
- package/dist/gs/unicode/unicode.js +362 -278
- package/dist/gs/unicode/unicode.js.map +1 -1
- package/go.sum +13 -0
- package/gs/builtin/builtin.ts +83 -93
- package/gs/builtin/hostio.ts +1 -1
- package/gs/builtin/index.ts +1 -0
- package/gs/builtin/panic.test.ts +189 -0
- package/gs/builtin/panic.ts +107 -0
- package/gs/builtin/runtime-contract.test.ts +5 -5
- package/gs/builtin/slice.test.ts +23 -0
- package/gs/builtin/slice.ts +133 -95
- package/gs/builtin/type.ts +16 -3
- package/gs/builtin/varRef.ts +4 -2
- package/gs/builtin/wide-int.test.ts +41 -0
- package/gs/bytes/bytes.gs.ts +54 -41
- package/gs/bytes/bytes.test.ts +18 -1
- package/gs/bytes/reader.gs.ts +7 -8
- package/gs/cmp/index.test.ts +55 -0
- package/gs/cmp/index.ts +45 -9
- package/gs/context/context.ts +3 -3
- package/gs/embed/index.ts +2 -2
- package/gs/encoding/binary/index.test.ts +104 -0
- package/gs/encoding/binary/index.ts +259 -11
- package/gs/encoding/json/index.test.ts +107 -0
- package/gs/encoding/json/index.ts +400 -29
- package/gs/errors/errors.test.ts +44 -1
- package/gs/errors/errors.ts +15 -31
- package/gs/fmt/fmt.test.ts +70 -2
- package/gs/fmt/fmt.ts +128 -34
- package/gs/golang.org/x/crypto/cryptobyte/index.ts +1 -1
- package/gs/internal/bytealg/index.test.ts +26 -1
- package/gs/internal/bytealg/index.ts +44 -8
- package/gs/internal/byteorder/index.ts +6 -4
- package/gs/io/fs/format.ts +2 -2
- package/gs/io/fs/fs.ts +2 -2
- package/gs/io/fs/stat.test.ts +2 -2
- package/gs/io/fs/sub.test.ts +2 -2
- package/gs/io/fs/walk.test.ts +2 -2
- package/gs/io/io.test.ts +47 -5
- package/gs/io/io.ts +73 -73
- package/gs/io/limit.test.ts +103 -0
- package/gs/math/bits/index.test.ts +128 -0
- package/gs/math/bits/index.ts +26 -8
- package/gs/math/copysign.gs.test.ts +3 -1
- package/gs/math/copysign.gs.ts +10 -22
- package/gs/math/pow.gs.test.ts +4 -5
- package/gs/math/pow.gs.ts +5 -0
- package/gs/math/signbit.gs.test.ts +2 -1
- package/gs/math/signbit.gs.ts +6 -3
- package/gs/mime/index.ts +1 -0
- package/gs/net/http/index.test.ts +683 -2
- package/gs/net/http/index.ts +598 -57
- package/gs/net/http/meta.json +3 -0
- package/gs/os/stat.gs.ts +2 -2
- package/gs/os/types.gs.ts +2 -2
- package/gs/os/types_js.gs.ts +9 -9
- package/gs/os/types_unix.gs.ts +2 -2
- package/gs/os/zero_copy_posix.gs.ts +2 -2
- package/gs/path/filepath/match.test.ts +16 -0
- package/gs/path/filepath/match.ts +8 -4
- package/gs/path/filepath/path.test.ts +91 -9
- package/gs/path/filepath/path.ts +223 -49
- package/gs/path/match.test.ts +32 -0
- package/gs/path/match.ts +6 -3
- package/gs/reflect/deepequal.test.ts +1 -1
- package/gs/reflect/field.test.ts +1 -1
- package/gs/reflect/function-types.test.ts +6 -6
- package/gs/reflect/sliceat.test.ts +13 -13
- package/gs/reflect/structof.test.ts +4 -4
- package/gs/reflect/type.ts +34 -14
- package/gs/reflect/typefor.test.ts +5 -5
- package/gs/runtime/pprof/index.test.ts +20 -0
- package/gs/runtime/trace/index.test.ts +3 -0
- package/gs/slices/slices.test.ts +31 -0
- package/gs/slices/slices.ts +11 -11
- package/gs/strconv/append.test.ts +99 -0
- package/gs/strconv/atof.gs.ts +156 -42
- package/gs/strconv/atof.test.ts +45 -0
- package/gs/strconv/atoi.gs.ts +87 -69
- package/gs/strconv/atoi.test.ts +49 -0
- package/gs/strconv/ftoa.gs.ts +85 -10
- package/gs/strconv/ftoa.test.ts +43 -0
- package/gs/strconv/itoa.gs.ts +10 -9
- package/gs/strconv/quote.gs.ts +335 -108
- package/gs/strconv/quote.test.ts +111 -0
- package/gs/strings/reader.test.ts +10 -10
- package/gs/strings/reader.ts +9 -9
- package/gs/strings/strings.test.ts +18 -5
- package/gs/strings/strings.ts +81 -68
- package/gs/sync/atomic/doc_64.gs.ts +24 -24
- package/gs/sync/atomic/doc_64.test.ts +5 -5
- package/gs/sync/atomic/type.gs.ts +28 -28
- package/gs/sync/sync.test.ts +109 -1
- package/gs/sync/sync.ts +46 -12
- package/gs/syscall/fs.ts +8 -8
- package/gs/syscall/net.test.ts +1 -1
- package/gs/time/parse.test.ts +45 -0
- package/gs/time/time.test.ts +46 -23
- package/gs/time/time.ts +69 -66
- package/gs/unicode/gen.go +198 -0
- package/gs/unicode/tables.ts +646 -0
- package/gs/unicode/unicode.test.ts +69 -0
- package/gs/unicode/unicode.ts +396 -312
- package/package.json +1 -1
- package/dist/gs/github.com/aperturerobotics/util/conc/index.d.ts +0 -20
- package/dist/gs/github.com/aperturerobotics/util/conc/index.js +0 -134
- package/dist/gs/github.com/aperturerobotics/util/conc/index.js.map +0 -1
- package/gs/github.com/aperturerobotics/util/conc/index.test.ts +0 -30
- package/gs/github.com/aperturerobotics/util/conc/index.ts +0 -172
- package/gs/github.com/aperturerobotics/util/conc/meta.json +0 -9
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { int64Div, pointerValue } from './builtin.js'
|
|
4
|
+
import {
|
|
5
|
+
GoPanic,
|
|
6
|
+
RuntimeError,
|
|
7
|
+
panic,
|
|
8
|
+
panicValue,
|
|
9
|
+
recover,
|
|
10
|
+
recovered,
|
|
11
|
+
runtimePanic,
|
|
12
|
+
} from './panic.js'
|
|
13
|
+
import { goSlice, index, makeSlice } from './slice.js'
|
|
14
|
+
|
|
15
|
+
// captureThrow returns whatever value the callback throws, or undefined if it
|
|
16
|
+
// did not throw. Go panics surface as a thrown GoPanic in the transpiled
|
|
17
|
+
// runtime, so tests assert on the caught value's shape. Catching here models a
|
|
18
|
+
// program-exit boundary that fully consumes the panic, so it drains the in-flight
|
|
19
|
+
// stack to keep cases independent.
|
|
20
|
+
function captureThrow(fn: () => unknown): unknown {
|
|
21
|
+
try {
|
|
22
|
+
fn()
|
|
23
|
+
} catch (e) {
|
|
24
|
+
if (e instanceof GoPanic) {
|
|
25
|
+
e.recovered = true
|
|
26
|
+
recovered(e)
|
|
27
|
+
}
|
|
28
|
+
return e
|
|
29
|
+
}
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('runtime panics route through GoPanic', () => {
|
|
34
|
+
it('runtimePanic throws a GoPanic carrying a RuntimeError', () => {
|
|
35
|
+
const caught = captureThrow(() => runtimePanic('runtime error: boom'))
|
|
36
|
+
|
|
37
|
+
expect(caught).toBeInstanceOf(GoPanic)
|
|
38
|
+
const value = (caught as GoPanic).value
|
|
39
|
+
expect(value).toBeInstanceOf(RuntimeError)
|
|
40
|
+
// The recovered value implements the Go error interface.
|
|
41
|
+
expect((value as RuntimeError).Error()).toBe('runtime error: boom')
|
|
42
|
+
// The unrecovered crash text matches Go's "panic: <value>" shape.
|
|
43
|
+
expect((caught as GoPanic).message).toBe('panic: runtime error: boom')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('integer divide by zero panics with the Go message', () => {
|
|
47
|
+
const caught = captureThrow(() => int64Div(1n, 0n))
|
|
48
|
+
|
|
49
|
+
expect(caught).toBeInstanceOf(GoPanic)
|
|
50
|
+
expect(((caught as GoPanic).value as RuntimeError).Error()).toBe(
|
|
51
|
+
'runtime error: integer divide by zero',
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('nil pointer dereference panics with the Go message', () => {
|
|
56
|
+
const caught = captureThrow(() => pointerValue(null))
|
|
57
|
+
|
|
58
|
+
expect(caught).toBeInstanceOf(GoPanic)
|
|
59
|
+
expect(((caught as GoPanic).value as RuntimeError).Error()).toBe(
|
|
60
|
+
'runtime error: invalid memory address or nil pointer dereference',
|
|
61
|
+
)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('index out of range panics with the Go message', () => {
|
|
65
|
+
const caught = captureThrow(() => index(new Uint8Array([1, 2, 3]), 5))
|
|
66
|
+
|
|
67
|
+
expect(caught).toBeInstanceOf(GoPanic)
|
|
68
|
+
expect(((caught as GoPanic).value as RuntimeError).Error()).toBe(
|
|
69
|
+
'runtime error: index out of range [5] with length 3',
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('slice bounds out of range panics with the Go message', () => {
|
|
74
|
+
const caught = captureThrow(() => goSlice([1, 2, 3], 2, 1))
|
|
75
|
+
|
|
76
|
+
expect(caught).toBeInstanceOf(GoPanic)
|
|
77
|
+
expect(((caught as GoPanic).value as RuntimeError).Error()).toBe(
|
|
78
|
+
'runtime error: slice bounds out of range [2:1]',
|
|
79
|
+
)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('makeslice with negative capacity panics with the Go message', () => {
|
|
83
|
+
const caught = captureThrow(() => makeSlice<number>(1, -1))
|
|
84
|
+
|
|
85
|
+
expect(caught).toBeInstanceOf(GoPanic)
|
|
86
|
+
expect(((caught as GoPanic).value as RuntimeError).Error()).toBe(
|
|
87
|
+
'runtime error: makeslice: cap out of range',
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('panic carries an arbitrary value that panicValue unwraps', () => {
|
|
92
|
+
const caught = captureThrow(() => panic('boom'))
|
|
93
|
+
|
|
94
|
+
expect(caught).toBeInstanceOf(GoPanic)
|
|
95
|
+
expect(panicValue(caught)).toBe('boom')
|
|
96
|
+
// panicValue passes non-panic values through unchanged.
|
|
97
|
+
expect(panicValue('plain')).toBe('plain')
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
describe('recover consumes the in-flight panic', () => {
|
|
102
|
+
// runWithRecover mirrors the code generated for a Go func that defers a
|
|
103
|
+
// function calling recover(): the deferreds run during stack unwinding (here,
|
|
104
|
+
// directly in the catch before recovered() is consulted), then recovered()
|
|
105
|
+
// decides whether the panic is swallowed or rethrown.
|
|
106
|
+
function runWithRecover(
|
|
107
|
+
body: () => void,
|
|
108
|
+
deferred: () => void,
|
|
109
|
+
): { rethrew: unknown; finished: boolean } {
|
|
110
|
+
try {
|
|
111
|
+
body()
|
|
112
|
+
} catch (e) {
|
|
113
|
+
deferred()
|
|
114
|
+
if (!recovered(e)) {
|
|
115
|
+
// This frame is the outermost boundary in the test. In a real program an
|
|
116
|
+
// unrecovered panic propagates to the entrypoint and crashes the
|
|
117
|
+
// process, leaving nothing in flight; drain it here to model that exit
|
|
118
|
+
// so later cases start with an empty stack.
|
|
119
|
+
if (e instanceof GoPanic) {
|
|
120
|
+
e.recovered = true
|
|
121
|
+
recovered(e)
|
|
122
|
+
}
|
|
123
|
+
return { rethrew: e, finished: false }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { rethrew: undefined, finished: true }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
it('recover returns the panic value and swallows the panic', () => {
|
|
130
|
+
let seen: unknown = 'unset'
|
|
131
|
+
const outcome = runWithRecover(
|
|
132
|
+
() => panic('boom'),
|
|
133
|
+
() => {
|
|
134
|
+
seen = recover()
|
|
135
|
+
},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
expect(seen).toBe('boom')
|
|
139
|
+
expect(outcome.finished).toBe(true)
|
|
140
|
+
expect(outcome.rethrew).toBeUndefined()
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('a deferred that does not recover lets the panic propagate', () => {
|
|
144
|
+
const outcome = runWithRecover(
|
|
145
|
+
() => panic('boom'),
|
|
146
|
+
() => {
|
|
147
|
+
// no recover() call
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
expect(outcome.finished).toBe(false)
|
|
152
|
+
expect(outcome.rethrew).toBeInstanceOf(GoPanic)
|
|
153
|
+
expect((outcome.rethrew as GoPanic).value).toBe('boom')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('recover returns nil when no panic is in flight', () => {
|
|
157
|
+
expect(recover()).toBeNull()
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('recover returns nil a second time within the same panic', () => {
|
|
161
|
+
let first: unknown = 'unset'
|
|
162
|
+
let second: unknown = 'unset'
|
|
163
|
+
runWithRecover(
|
|
164
|
+
() => panic('once'),
|
|
165
|
+
() => {
|
|
166
|
+
first = recover()
|
|
167
|
+
second = recover()
|
|
168
|
+
},
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
expect(first).toBe('once')
|
|
172
|
+
expect(second).toBeNull()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('recovers a runtime fault as its RuntimeError value', () => {
|
|
176
|
+
let seen: unknown = 'unset'
|
|
177
|
+
runWithRecover(
|
|
178
|
+
() => int64Div(1n, 0n),
|
|
179
|
+
() => {
|
|
180
|
+
seen = recover()
|
|
181
|
+
},
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
expect(seen).toBeInstanceOf(RuntimeError)
|
|
185
|
+
expect((seen as RuntimeError).Error()).toBe(
|
|
186
|
+
'runtime error: integer divide by zero',
|
|
187
|
+
)
|
|
188
|
+
})
|
|
189
|
+
})
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// GoPanic carries a Go panic value as it unwinds the JavaScript call stack. A
|
|
2
|
+
// deferred recover() reads its value and sets recovered; an unrecovered GoPanic
|
|
3
|
+
// surfaces as a thrown error whose message is "panic: <value>", matching Go's
|
|
4
|
+
// crash output.
|
|
5
|
+
export class GoPanic extends Error {
|
|
6
|
+
recovered = false
|
|
7
|
+
|
|
8
|
+
constructor(public readonly value: unknown) {
|
|
9
|
+
super(`panic: ${formatPanicValue(value)}`)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// panicStack holds the GoPanics currently unwinding the stack, innermost last.
|
|
14
|
+
// panic() and runtimePanic() push before throwing; a deferred recover() consults
|
|
15
|
+
// the top entry, and the recover-aware catch generated for a defer+recover
|
|
16
|
+
// function removes it once handled. Empty when no panic is in flight, so a
|
|
17
|
+
// recover() outside a panic returns nil.
|
|
18
|
+
const panicStack: GoPanic[] = []
|
|
19
|
+
|
|
20
|
+
// RuntimeError is the value carried by a panic from a Go runtime fault: index
|
|
21
|
+
// out of range, slice bounds out of range, integer divide by zero, or nil
|
|
22
|
+
// pointer dereference. It implements the Go error interface (Error()), so a
|
|
23
|
+
// recovered runtime panic exposes the same shape as Go's runtime.Error.
|
|
24
|
+
export class RuntimeError extends Error {
|
|
25
|
+
constructor(message: string) {
|
|
26
|
+
super(message)
|
|
27
|
+
this.name = 'RuntimeError'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Error(): string {
|
|
31
|
+
return this.message
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Implementation of Go's built-in panic function.
|
|
37
|
+
* @param args Arguments passed to panic
|
|
38
|
+
*/
|
|
39
|
+
export function panic(...args: unknown[]): never {
|
|
40
|
+
const value = args.length === 1 ? args[0] : args
|
|
41
|
+
const goPanic = new GoPanic(value)
|
|
42
|
+
panicStack.push(goPanic)
|
|
43
|
+
throw goPanic
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// runtimePanic raises a Go runtime panic carrying a RuntimeError with the given
|
|
47
|
+
// Go-formatted message (for example "runtime error: index out of range [5] with
|
|
48
|
+
// length 3"). Every runtime fault routes through here so recover() observes a
|
|
49
|
+
// GoPanic whose value is a runtime.Error, matching Go.
|
|
50
|
+
export function runtimePanic(message: string): never {
|
|
51
|
+
const goPanic = new GoPanic(new RuntimeError(message))
|
|
52
|
+
panicStack.push(goPanic)
|
|
53
|
+
throw goPanic
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function panicValue(value: unknown): unknown {
|
|
57
|
+
if (value instanceof GoPanic) {
|
|
58
|
+
return value.value
|
|
59
|
+
}
|
|
60
|
+
return value
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatPanicValue(value: unknown): string {
|
|
64
|
+
if (value instanceof Error) {
|
|
65
|
+
return value.message
|
|
66
|
+
}
|
|
67
|
+
if (
|
|
68
|
+
value !== null &&
|
|
69
|
+
typeof value === 'object' &&
|
|
70
|
+
'Error' in value &&
|
|
71
|
+
typeof (value as { Error?: unknown }).Error === 'function'
|
|
72
|
+
) {
|
|
73
|
+
return String((value as { Error(): string }).Error())
|
|
74
|
+
}
|
|
75
|
+
return String(value)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// recover stops the in-flight panic and returns its value, matching Go's
|
|
79
|
+
// built-in recover. It returns nil when no panic is unwinding (an empty stack)
|
|
80
|
+
// or when the current panic was already recovered. Generated code only routes a
|
|
81
|
+
// recover() call to a useful panic when it runs inside a deferred function whose
|
|
82
|
+
// enclosing function is unwinding, so a recover() in ordinary control flow reads
|
|
83
|
+
// an empty stack and returns nil.
|
|
84
|
+
export function recover(): any {
|
|
85
|
+
const goPanic = panicStack[panicStack.length - 1]
|
|
86
|
+
if (goPanic === undefined || goPanic.recovered) {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
goPanic.recovered = true
|
|
90
|
+
return goPanic.value
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// recovered reports whether err is a GoPanic that a deferred recover() consumed,
|
|
94
|
+
// and removes it from the in-flight stack. The catch block generated for a
|
|
95
|
+
// defer+recover function calls this: true means swallow the panic and return the
|
|
96
|
+
// function's named results; false means rethrow so an outer frame can recover or
|
|
97
|
+
// the program crashes with the Go panic message.
|
|
98
|
+
export function recovered(err: unknown): boolean {
|
|
99
|
+
if (err instanceof GoPanic && err.recovered) {
|
|
100
|
+
const idx = panicStack.indexOf(err)
|
|
101
|
+
if (idx !== -1) {
|
|
102
|
+
panicStack.splice(idx, 1)
|
|
103
|
+
}
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
return false
|
|
107
|
+
}
|
|
@@ -139,8 +139,8 @@ describe('builtin runtime contract helpers', () => {
|
|
|
139
139
|
expect(uint(uint64Shl(1n, 63), 32)).toBe(0)
|
|
140
140
|
expect(uint(uint64Shr(uint64Shl(1n, 63), 60), 32)).toBe(8)
|
|
141
141
|
expect(uint(uint64Mul(0xffffffffffffffffn, 3), 32)).toBe(0xfffffffd)
|
|
142
|
-
expect(uint64Div(0xffffffffffffffffn, 4114)).toBe(
|
|
143
|
-
expect(uint64Mod(0xffffffffffffffffn, 4114)).toBe(
|
|
142
|
+
expect(uint64Div(0xffffffffffffffffn, 4114)).toBe(4483895010624587n)
|
|
143
|
+
expect(uint64Mod(0xffffffffffffffffn, 4114)).toBe(697n)
|
|
144
144
|
expect(uint(uint64Add(0xffffffffffffffffn, 2), 32)).toBe(1)
|
|
145
145
|
expect(uint(uint64Sub(1n, 2), 32)).toBe(0xffffffff)
|
|
146
146
|
expect(uint(uint64And(0xf0n, 0x3cn), 32)).toBe(0x30)
|
|
@@ -371,8 +371,8 @@ describe('builtin runtime contract helpers', () => {
|
|
|
371
371
|
|
|
372
372
|
it('asserts fixed-width numeric values by runtime type name', () => {
|
|
373
373
|
expect(
|
|
374
|
-
typeAssertTuple<
|
|
375
|
-
).toEqual([
|
|
374
|
+
typeAssertTuple<bigint>(13n, { kind: TypeKind.Basic, name: 'uint64' }),
|
|
375
|
+
).toEqual([13n, true])
|
|
376
376
|
expect(
|
|
377
377
|
typeAssertTuple<number>(13, { kind: TypeKind.Basic, name: 'int32' }),
|
|
378
378
|
).toEqual([13, true])
|
|
@@ -530,7 +530,7 @@ describe('builtin runtime contract helpers', () => {
|
|
|
530
530
|
expect(array).toEqual([9, 3])
|
|
531
531
|
expect(source![0]).toBe(2)
|
|
532
532
|
expect(() => sliceToArray<number>(source, 3)).toThrow(
|
|
533
|
-
'cannot convert slice with length 2 to array with length 3',
|
|
533
|
+
'cannot convert slice with length 2 to array or pointer to array with length 3',
|
|
534
534
|
)
|
|
535
535
|
|
|
536
536
|
expect(sliceToArray<number>(new Uint8Array([4, 5, 6]), 2, 'byte')).toEqual(
|
package/gs/builtin/slice.test.ts
CHANGED
|
@@ -7,12 +7,35 @@ import {
|
|
|
7
7
|
copy,
|
|
8
8
|
indexString,
|
|
9
9
|
len,
|
|
10
|
+
runeToString,
|
|
11
|
+
runesToString,
|
|
10
12
|
sliceString,
|
|
11
13
|
stringCompare,
|
|
12
14
|
stringEqual,
|
|
13
15
|
stringToBytes,
|
|
14
16
|
} from './slice.js'
|
|
15
17
|
|
|
18
|
+
describe('rune to string encoding (Go string(rune) semantics)', () => {
|
|
19
|
+
it('preserves astral-plane runes above U+FFFF', () => {
|
|
20
|
+
// U+1F600 grinning face; fromCharCode would have truncated to a broken unit.
|
|
21
|
+
expect(runeToString(0x1f600)).toBe('😀')
|
|
22
|
+
expect(runeToString(0x1f600).codePointAt(0)).toBe(0x1f600)
|
|
23
|
+
// CJU+597D '好'
|
|
24
|
+
expect(runeToString(0x597d)).toBe('好')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('maps invalid and surrogate runes to U+FFFD, never throwing', () => {
|
|
28
|
+
expect(runeToString(-1)).toBe('�')
|
|
29
|
+
expect(runeToString(0x110000)).toBe('�')
|
|
30
|
+
expect(runeToString(0xd800)).toBe('�')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('encodes a rune slice with astral planes intact', () => {
|
|
34
|
+
expect(runesToString([0x48, 0x69, 0x1f600] as never)).toBe('Hi😀')
|
|
35
|
+
expect(runesToString([] as never)).toBe('')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
16
39
|
describe('builtin string byte representation', () => {
|
|
17
40
|
it('appends large byte slices without JavaScript argument spreading', () => {
|
|
18
41
|
const dst = new Uint8Array(0)
|