forked from defunkt/ReefVM
don't need vm for simple toValue calls
This commit is contained in:
parent
15884ac239
commit
47e227f50c
39
src/value.ts
39
src/value.ts
|
|
@ -66,23 +66,9 @@ export function toValue(v: any, vm?: VM): Value /* throws */ {
|
|||
case 'function':
|
||||
if ((v as any)[REEF_FUNCTION])
|
||||
return (v as any)[REEF_FUNCTION]
|
||||
if (!vm) {
|
||||
const fnName = v.name || '<anonymous>'
|
||||
const fnStr = v.toString().slice(0, 100)
|
||||
const stack = new Error().stack || ''
|
||||
const stackLines = stack.split('\n')
|
||||
.slice(1)
|
||||
.filter(line => !line.includes('toValue'))
|
||||
.map(line => ' ' + line.trim())
|
||||
.join('\n')
|
||||
throw new Error(
|
||||
`can't toValue() function without a vm\n` +
|
||||
` Function: ${fnName}\n` +
|
||||
` Source: ${fnStr}${v.toString().length > 100 ? '...' : ''}\n` +
|
||||
` Called from:\n${stackLines}`
|
||||
)
|
||||
}
|
||||
return { type: 'native', fn: wrapNative(vm, v), value: '<function>' }
|
||||
|
||||
let fn = vm ? wrapNative(vm, v) : cantCallFunctionWithoutVM(v)
|
||||
return { type: 'native', fn, value: '<function>' }
|
||||
case 'object':
|
||||
const dict: Dict = new Map()
|
||||
|
||||
|
|
@ -94,6 +80,25 @@ export function toValue(v: any, vm?: VM): Value /* throws */ {
|
|||
}
|
||||
}
|
||||
|
||||
function cantCallFunctionWithoutVM(fn: Function) {
|
||||
const name = fn.name || '<anonymous>'
|
||||
const str = fn.toString().slice(0, 100)
|
||||
return (...args: Value[]) => {
|
||||
const stack = new Error().stack || ''
|
||||
const stackLines = stack.split('\n')
|
||||
.slice(1)
|
||||
.filter(line => !line.includes('toValue'))
|
||||
.map(line => ' ' + line.trim())
|
||||
.join('\n')
|
||||
throw new Error(
|
||||
`can't call function that was converted without a vm\n` +
|
||||
` Function: ${name}\n` +
|
||||
` Source: ${str}${str.length > 100 ? '...' : ''}\n` +
|
||||
` Called from:\n${stackLines}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function toNumber(v: Value): number {
|
||||
switch (v.type) {
|
||||
case 'number':
|
||||
|
|
|
|||
|
|
@ -204,38 +204,81 @@ test("fromValue - async native function roundtrip", async () => {
|
|||
expect(result).toBe(12)
|
||||
})
|
||||
|
||||
test("toValue - throws helpful error when converting function without VM", () => {
|
||||
test("toValue - throws helpful error when calling function converted without VM", async () => {
|
||||
function myFunction(x: number) {
|
||||
return x * 2
|
||||
}
|
||||
|
||||
expect(() => toValue(myFunction)).toThrow(/can't toValue\(\) function without a vm/)
|
||||
expect(() => toValue(myFunction)).toThrow(/Function: myFunction/)
|
||||
expect(() => toValue(myFunction)).toThrow(/Source:/)
|
||||
expect(() => toValue(myFunction)).toThrow(/Called from:/)
|
||||
const value = toValue(myFunction)
|
||||
expect(value.type).toBe('native')
|
||||
|
||||
// Error is thrown when calling the function, not when converting
|
||||
await expect(async () => {
|
||||
await value.fn({ type: 'number', value: 5 })
|
||||
}).toThrow(/can't call function that was converted without a vm/)
|
||||
|
||||
await expect(async () => {
|
||||
await value.fn({ type: 'number', value: 5 })
|
||||
}).toThrow(/Function: myFunction/)
|
||||
|
||||
await expect(async () => {
|
||||
await value.fn({ type: 'number', value: 5 })
|
||||
}).toThrow(/Source:/)
|
||||
|
||||
await expect(async () => {
|
||||
await value.fn({ type: 'number', value: 5 })
|
||||
}).toThrow(/Called from:/)
|
||||
})
|
||||
|
||||
test("toValue - error message shows function name from binding", () => {
|
||||
test("toValue - error message shows function info for arrow functions", async () => {
|
||||
const anonymousFn = (x: number) => x * 2
|
||||
|
||||
expect(() => toValue(anonymousFn)).toThrow(/Function: anonymousFn/)
|
||||
expect(() => toValue(anonymousFn)).toThrow(/Source:/)
|
||||
const value = toValue(anonymousFn)
|
||||
expect(value.type).toBe('native')
|
||||
|
||||
// Arrow functions show as <anonymous>
|
||||
await expect(async () => {
|
||||
await value.fn({ type: 'number', value: 5 })
|
||||
}).toThrow(/Function: <anonymous>/)
|
||||
|
||||
await expect(async () => {
|
||||
await value.fn({ type: 'number', value: 5 })
|
||||
}).toThrow(/Source:/)
|
||||
})
|
||||
|
||||
test("toValue - error when function is nested in object without VM", () => {
|
||||
test("toValue - error when function is nested in object without VM", async () => {
|
||||
const obj = {
|
||||
name: "test",
|
||||
handler: (x: number) => x * 2
|
||||
}
|
||||
|
||||
expect(() => toValue(obj)).toThrow(/can't toValue\(\) function without a vm/)
|
||||
expect(() => toValue(obj)).toThrow(/Function: handler/)
|
||||
const value = toValue(obj)
|
||||
expect(value.type).toBe('dict')
|
||||
|
||||
const handlerValue = value.value.get('handler')!
|
||||
expect(handlerValue.type).toBe('native')
|
||||
|
||||
await expect(async () => {
|
||||
await (handlerValue as any).fn({ type: 'number', value: 5 })
|
||||
}).toThrow(/can't call function that was converted without a vm/)
|
||||
|
||||
await expect(async () => {
|
||||
await (handlerValue as any).fn({ type: 'number', value: 5 })
|
||||
}).toThrow(/Function: handler/)
|
||||
})
|
||||
|
||||
test("toValue - error when function is nested in array without VM", () => {
|
||||
test("toValue - error when function is nested in array without VM", async () => {
|
||||
const arr = [1, 2, (x: number) => x * 2]
|
||||
|
||||
expect(() => toValue(arr)).toThrow(/can't toValue\(\) function without a vm/)
|
||||
const value = toValue(arr)
|
||||
expect(value.type).toBe('array')
|
||||
|
||||
const fnValue = value.value[2]!
|
||||
expect(fnValue.type).toBe('native')
|
||||
|
||||
await expect(async () => {
|
||||
await (fnValue as any).fn({ type: 'number', value: 5 })
|
||||
}).toThrow(/can't call function that was converted without a vm/)
|
||||
})
|
||||
|
||||
test("fromValue - throws helpful error when converting function without VM", async () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user