not iterable error

This commit is contained in:
Dmitry Vasilev
2023-07-06 18:34:03 +03:00
parent 65bfacc180
commit 2f577d955d
6 changed files with 91 additions and 22 deletions

View File

@@ -145,20 +145,30 @@ export const find_node = (node, pred) => {
) )
} }
// TODO refactor, have explicit information if node is error origin, without
// guessing. See also color.js
// TODO check if return result is null and throw early
export const find_error_origin_node = node => export const find_error_origin_node = node =>
find_node( find_node(
// TODO do not go inside function_expr
node, node,
n => n.result != null && !n.result.ok && ( n => n.result != null && !n.result.ok && (
n.result.error != null n.result.error != null
|| ||
// In case if throw null or throw undefined // node has no error, but its children also have no error, so this node
n.type == 'throw' // is error origin
|| n.children.find(c => find_error_origin_node(c) != null) == null
// await can also throw null &&
n.type == 'unary' && n.operator == 'await' (
// or function call throwing null or undefined // In case if throw null or throw undefined
|| n.type == 'throw'
n.type == 'function_call' ||
// await can also throw null
n.type == 'unary' && n.operator == 'await'
// or function call throwing null or undefined
||
n.type == 'function_call'
)
) )
) )

View File

@@ -13,6 +13,8 @@ const node_to_color = node => ({
? null ? null
: node.result.ok : node.result.ok
? {ok: true} ? {ok: true}
// node.result.error may be null, for example if throw null
// See find_error_origin_node
: node.result.error == null : node.result.error == null
? {ok: false, error_origin: false} ? {ok: false, error_origin: false}
: {ok: false, error_origin: true} : {ok: false, error_origin: true}

View File

@@ -171,7 +171,7 @@ const codegen = (node, node_cxt, parent) => {
} else if(node.type == 'object_literal'){ } else if(node.type == 'object_literal'){
const elements = const elements =
node.elements.map(el => { node.elements.map(el => {
if(el.type == 'spread'){ if(el.type == 'object_spread'){
return do_codegen(el) return do_codegen(el)
} else if(el.type == 'identifier') { } else if(el.type == 'identifier') {
return el.value return el.value
@@ -241,7 +241,7 @@ const codegen = (node, node_cxt, parent) => {
+ node.operator + node.operator
+ ' ' + ' '
+ do_codegen(node.args[1]) + do_codegen(node.args[1])
} else if(node.type == 'spread'){ } else if(node.type == 'array_spread' || node.type == 'object_spread'){
return '...(' + do_codegen(node.expr) + ')' return '...(' + do_codegen(node.expr) + ')'
} else if(node.type == 'new') { } else if(node.type == 'new') {
const args = `[${node.args.children.map(do_codegen).join(',')}]` const args = `[${node.args.children.map(do_codegen).join(',')}]`
@@ -510,8 +510,24 @@ const do_eval_frame_expr = (node, scope, callsleft, context) => {
// TODO exprs inside backtick string // TODO exprs inside backtick string
// Pass scope for backtick string // Pass scope for backtick string
return {...eval_codestring(node.value, scope), calls: callsleft} return {...eval_codestring(node.value, scope), calls: callsleft}
} else if(node.type == 'array_spread') {
const result = eval_children(node, scope, callsleft, context)
if(!result.ok) {
return result
}
const child = result.children[0]
if((typeof(child.result.value?.[Symbol.iterator])) == 'function') {
return result
} else {
return {
ok: false,
children: result.children,
calls: result.calls,
error: new TypeError(child.string + ' is not iterable'),
}
}
} else if([ } else if([
'spread', 'object_spread',
'key_value_pair', 'key_value_pair',
'computed_property' 'computed_property'
].includes(node.type)) { ].includes(node.type)) {
@@ -523,8 +539,7 @@ const do_eval_frame_expr = (node, scope, callsleft, context) => {
} }
const value = children.reduce( const value = children.reduce(
(arr, el) => { (arr, el) => {
if(el.type == 'spread') { if(el.type == 'array_spread') {
// TODO check if iterable and throw error
return [...arr, ...el.children[0].result.value] return [...arr, ...el.children[0].result.value]
} else { } else {
return [...arr, el.result.value] return [...arr, el.result.value]
@@ -540,7 +555,7 @@ const do_eval_frame_expr = (node, scope, callsleft, context) => {
} }
const value = children.reduce( const value = children.reduce(
(value, el) => { (value, el) => {
if(el.type == 'spread'){ if(el.type == 'object_spread'){
return {...value, ...el.children[0].result.value} return {...value, ...el.children[0].result.value}
} else if(el.type == 'identifier') { } else if(el.type == 'identifier') {
// TODO check that it works // TODO check that it works

View File

@@ -828,7 +828,12 @@ const array_element = either(
literal('...'), literal('...'),
cxt => expr(cxt), cxt => expr(cxt),
]), ]),
({value, ...node}) => ({...node, type: 'spread', not_evaluatable: true, children: [value]}) ({value, ...node}) => ({
...node,
type: 'array_spread',
not_evaluatable: true,
children: [value]
})
), ),
cxt => expr(cxt), cxt => expr(cxt),
) )
@@ -857,7 +862,12 @@ const object_literal =
literal('...'), literal('...'),
cxt => expr(cxt), cxt => expr(cxt),
]), ]),
({value, ...node}) => ({...node, type: 'spread', children: [value], not_evaluatable: true}) ({value, ...node}) => ({
...node,
type: 'object_spread',
children: [value],
not_evaluatable: true
})
), ),
// Or key-value pair // Or key-value pair
@@ -1456,7 +1466,7 @@ const update_children_not_rec = (node, children = node.children) => {
} }
} else if(node.type == 'call_args') { } else if(node.type == 'call_args') {
return node return node
} else if(node.type == 'spread') { } else if(node.type == 'array_spread' || node.type == 'object_spread') {
return {...node, return {...node,
expr: children[0], expr: children[0],
} }

View File

@@ -837,6 +837,23 @@ export const tests = [
) )
}), }),
test('array spread not iterable', () => {
assert_code_error(
`[...null]`,
new Error('null is not iterable'),
)
}),
test('args spread not iterable', () => {
assert_code_error(
`
function x() {}
x(...null)
`,
new Error('null is not iterable'),
)
}),
test('module not found', () => { test('module not found', () => {
const parsed = parse_modules( const parsed = parse_modules(
'a', 'a',
@@ -2336,6 +2353,20 @@ const y = x()`
assert_equal(s2.value_explorer.result.error.message, 'boom') assert_equal(s2.value_explorer.result.error.message, 'boom')
}), }),
test('move_cursor error in fn args bug', () => {
const code = `
function x() {}
x(null.foo)
`
const i = test_initial_state(code)
const m = COMMANDS.move_cursor(i, code.indexOf('x(null'))
assert_equal(
m.value_explorer.result.error,
new Error("Cannot read properties of null (reading 'foo')")
)
}),
test('frame follows cursor toplevel', () => { test('frame follows cursor toplevel', () => {
const code = ` const code = `
const x = () => { const x = () => {

View File

@@ -1,3 +1,4 @@
import {find_error_origin_node} from '../src/ast_utils.js'
import {parse, print_debug_node, load_modules} from '../src/parse_js.js' import {parse, print_debug_node, load_modules} from '../src/parse_js.js'
import {eval_modules} from '../src/eval.js' import {eval_modules} from '../src/eval.js'
import {active_frame, pp_calltree} from '../src/calltree.js' import {active_frame, pp_calltree} from '../src/calltree.js'
@@ -81,9 +82,8 @@ export const assert_code_evals_to = (codestring, expected) => {
export const assert_code_error = (codestring, error) => { export const assert_code_error = (codestring, error) => {
const state = test_initial_state(codestring) const state = test_initial_state(codestring)
const frame = active_frame(state) const frame = active_frame(state)
const result = frame.children.at(-1).result assert_equal(frame.result.ok, false)
assert_equal(result.ok, false) assert_equal(find_error_origin_node(frame).result.error, error)
assert_equal(result.error, error)
} }
export const assert_code_evals_to_async = async (codestring, expected) => { export const assert_code_evals_to_async = async (codestring, expected) => {
@@ -168,9 +168,10 @@ export const test_deferred_calls_state = code => {
export const stringify = val => export const stringify = val =>
JSON.stringify(val, (key, value) => { JSON.stringify(val, (key, value) => {
// TODO do not use instanceof because currently not implemented in parser if(value instanceof Set){
if(value?.constructor == Set){
return [...value] return [...value]
} else if(value instanceof Error) {
return {message: value.message}
} else { } else {
return value return value
} }