import { describe, test, expect } from 'bun:test' import { Compiler } from '#compiler/compiler' import { VM } from 'reefvm' describe('Native Function Exceptions', () => { test('native function error caught by try/catch', async () => { const code = ` result = try: failing-fn catch e: 'caught: ' + e end result ` const compiler = new Compiler(code) const vm = new VM(compiler.bytecode) vm.set('failing-fn', () => { throw new Error('native function failed') }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'caught: native function failed' }) }) test('async native function error caught by try/catch', async () => { const code = ` result = try: async-fail catch e: 'async caught: ' + e end result ` const compiler = new Compiler(code) const vm = new VM(compiler.bytecode) vm.set('async-fail', async () => { await new Promise(resolve => setTimeout(resolve, 1)) throw new Error('async error') }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'async caught: async error' }) }) test('native function with arguments throwing error', async () => { const code = ` result = try: read-file missing.txt catch e: 'default content' end result ` const compiler = new Compiler(code) const vm = new VM(compiler.bytecode) vm.set('read-file', (path: string) => { if (path === 'missing.txt') { throw new Error('file not found') } return 'file contents' }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'default content' }) }) test('native function error with finally block', async () => { const code = ` cleanup-count = 0 result = try: failing-fn catch e: 'error handled' finally: cleanup-count = cleanup-count + 1 end cleanup-count ` const compiler = new Compiler(code) const vm = new VM(compiler.bytecode) vm.set('failing-fn', () => { throw new Error('native error') }) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 1 }) }) test('native function error without catch propagates', async () => { const code = ` failing-fn ` const compiler = new Compiler(code) const vm = new VM(compiler.bytecode) vm.set('failing-fn', () => { throw new Error('uncaught error') }) await expect(vm.run()).rejects.toThrow('uncaught error') }) test('native function in function-level catch', async () => { const code = ` safe-read = do path: read-file path catch e: 'default: ' + e end result = safe-read missing.txt result ` const compiler = new Compiler(code) const vm = new VM(compiler.bytecode) vm.set('read-file', (path: string) => { throw new Error('file not found: ' + path) }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'default: file not found: missing.txt' }) }) test('nested native function errors', async () => { const code = ` result = try: try: inner-fail catch e: throw 'wrapped: ' + e end catch e: 'outer caught: ' + e end result ` const compiler = new Compiler(code) const vm = new VM(compiler.bytecode) vm.set('inner-fail', () => { throw new Error('inner error') }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'outer caught: wrapped: inner error' }) }) test('native function error with multiple named args', async () => { const code = ` result = try: process-file path=missing.txt mode=strict catch e: 'error: ' + e end result ` const compiler = new Compiler(code) const vm = new VM(compiler.bytecode) vm.set('process-file', (path: string, mode: string = 'lenient') => { if (mode === 'strict' && path === 'missing.txt') { throw new Error('strict mode: file required') } return 'processed' }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'error: strict mode: file required' }) }) test('native function returning normally after other functions threw', async () => { const code = ` result1 = try: failing-fn catch e: 'caught' end result2 = success-fn result1 + ' then ' + result2 ` const compiler = new Compiler(code) const vm = new VM(compiler.bytecode) vm.set('failing-fn', () => { throw new Error('error') }) vm.set('success-fn', () => { return 'success' }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'caught then success' }) }) test('native function error message preserved', async () => { const code = ` result = try: throw-custom-message catch e: e end result ` const compiler = new Compiler(code) const vm = new VM(compiler.bytecode) vm.set('throw-custom-message', () => { throw new Error('This is a very specific error message with details') }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'This is a very specific error message with details' }) }) test('native function throwing non-Error value', async () => { const code = ` result = try: throw-string catch e: 'caught: ' + e end result ` const compiler = new Compiler(code) const vm = new VM(compiler.bytecode) vm.set('throw-string', () => { throw 'plain string error' }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'caught: plain string error' }) }) test('multiple native function calls with mixed success/failure', async () => { const code = ` r1 = try: success-fn catch e: 'error' end r2 = try: failing-fn catch e: 'caught' end r3 = try: success-fn catch e: 'error' end results = [r1 r2 r3] results ` const compiler = new Compiler(code) const vm = new VM(compiler.bytecode) vm.set('success-fn', () => 'ok') vm.set('failing-fn', () => { throw new Error('failed') }) const result = await vm.run() expect(result.type).toBe('array') const arr = result.value as any[] expect(arr.length).toBe(3) expect(arr[0]).toEqual({ type: 'string', value: 'ok' }) expect(arr[1]).toEqual({ type: 'string', value: 'caught' }) expect(arr[2]).toEqual({ type: 'string', value: 'ok' }) }) })