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':
|
case 'function':
|
||||||
if ((v as any)[REEF_FUNCTION])
|
if ((v as any)[REEF_FUNCTION])
|
||||||
return (v as any)[REEF_FUNCTION]
|
return (v as any)[REEF_FUNCTION]
|
||||||
if (!vm) {
|
|
||||||
const fnName = v.name || '<anonymous>'
|
let fn = vm ? wrapNative(vm, v) : cantCallFunctionWithoutVM(v)
|
||||||
const fnStr = v.toString().slice(0, 100)
|
return { type: 'native', fn, value: '<function>' }
|
||||||
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>' }
|
|
||||||
case 'object':
|
case 'object':
|
||||||
const dict: Dict = new Map()
|
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 {
|
export function toNumber(v: Value): number {
|
||||||
switch (v.type) {
|
switch (v.type) {
|
||||||
case 'number':
|
case 'number':
|
||||||
|
|
|
||||||
|
|
@ -204,38 +204,81 @@ test("fromValue - async native function roundtrip", async () => {
|
||||||
expect(result).toBe(12)
|
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) {
|
function myFunction(x: number) {
|
||||||
return x * 2
|
return x * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(() => toValue(myFunction)).toThrow(/can't toValue\(\) function without a vm/)
|
const value = toValue(myFunction)
|
||||||
expect(() => toValue(myFunction)).toThrow(/Function: myFunction/)
|
expect(value.type).toBe('native')
|
||||||
expect(() => toValue(myFunction)).toThrow(/Source:/)
|
|
||||||
expect(() => toValue(myFunction)).toThrow(/Called from:/)
|
// 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
|
const anonymousFn = (x: number) => x * 2
|
||||||
|
|
||||||
expect(() => toValue(anonymousFn)).toThrow(/Function: anonymousFn/)
|
const value = toValue(anonymousFn)
|
||||||
expect(() => toValue(anonymousFn)).toThrow(/Source:/)
|
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 = {
|
const obj = {
|
||||||
name: "test",
|
name: "test",
|
||||||
handler: (x: number) => x * 2
|
handler: (x: number) => x * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(() => toValue(obj)).toThrow(/can't toValue\(\) function without a vm/)
|
const value = toValue(obj)
|
||||||
expect(() => toValue(obj)).toThrow(/Function: handler/)
|
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]
|
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 () => {
|
test("fromValue - throws helpful error when converting function without VM", async () => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user