fix is not a function errors

This commit is contained in:
Dmitry Vasilev
2023-01-18 19:21:25 +08:00
parent 3047e89e8a
commit 096ca5e495
2 changed files with 76 additions and 61 deletions

View File

@@ -94,6 +94,14 @@ const codegen_function_expr = (node, cxt) => {
return `__trace(${call}, "${node.name}", ${argscount}, ${location}, ${get_closure})` return `__trace(${call}, "${node.name}", ${argscount}, ${location}, ${get_closure})`
} }
/*
in v8 `foo().bar().baz()` gives error `foo(...).bar(...).baz is not a function`
*/
const not_a_function_error = node => node.string.replaceAll(
new RegExp('\\(.*\\)', 'g'),
'(...)'
)
// TODO if statically can prove that function is hosted, then do not codegen // TODO if statically can prove that function is hosted, then do not codegen
// __trace // __trace
const codegen_function_call = (node, cxt) => { const codegen_function_call = (node, cxt) => {
@@ -102,6 +110,8 @@ const codegen_function_call = (node, cxt) => {
const args = `[${node.args.children.map(do_codegen).join(',')}]` const args = `[${node.args.children.map(do_codegen).join(',')}]`
const errormessage = not_a_function_error(node.fn)
let call let call
if(node.fn.type == 'member_access') { if(node.fn.type == 'member_access') {
const op = node.fn.is_optional_chaining ? '?.' : '' const op = node.fn.is_optional_chaining ? '?.' : ''
@@ -113,10 +123,11 @@ const codegen_function_call = (node, cxt) => {
return `( return `(
__obj = ${do_codegen(node.fn.object)}, __obj = ${do_codegen(node.fn.object)},
__fn = __obj${op}[${do_codegen(node.fn.property)}], __fn = __obj${op}[${do_codegen(node.fn.property)}],
__trace_call(__fn, __obj, ${args}) __trace_call(__fn, __obj, ${args}, ${JSON.stringify(errormessage)})
)` )`
} else { } else {
return `__trace_call(${do_codegen(node.fn)}, null, ${args})` return `__trace_call(${do_codegen(node.fn)}, null, ${args}, \
${JSON.stringify(errormessage)})`
} }
} }
@@ -227,7 +238,9 @@ const codegen = (node, cxt, parent) => {
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(',')}]`
return `__trace_call(${do_codegen(node.constructor)}, null, ${args}, true)` const errormessage = not_a_function_error(node.constructor)
return `__trace_call(${do_codegen(node.constructor)}, null, ${args}, \
${JSON.stringify(errormessage)}, true)`
} else if(node.type == 'grouping'){ } else if(node.type == 'grouping'){
return '(' + do_codegen(node.expr) + ')' return '(' + do_codegen(node.expr) + ')'
} else if(node.type == 'array_destructuring') { } else if(node.type == 'array_destructuring') {
@@ -591,7 +604,7 @@ export const eval_modules = (
return result return result
} }
const __trace_call = (fn, context, args, is_new = false) => { const __trace_call = (fn, context, args, errormessage, is_new = false) => {
if(fn != null && fn.__location != null && !is_new) { if(fn != null && fn.__location != null && !is_new) {
// Call will be traced, because tracing code is already embedded inside // Call will be traced, because tracing code is already embedded inside
// fn // fn
@@ -599,12 +612,11 @@ export const eval_modules = (
} }
if(typeof(fn) != 'function') { if(typeof(fn) != 'function') {
// Raise error throw new TypeError(
if(is_new) { errormessage
return new fn(...args) + ' is not a '
} else { + (is_new ? 'constructor' : 'function')
return fn.apply(context, args) )
}
} }
const children_copy = children const children_copy = children
@@ -900,8 +912,8 @@ const get_args_scope = (fn_node, args) => {
} }
} }
const eval_binary_expr = (node, scope, callsleft) => { const eval_binary_expr = (node, scope, callsleft, context) => {
const {ok, children, calls} = eval_children(node, scope, callsleft) const {ok, children, calls} = eval_children(node, scope, callsleft, context)
if(!ok) { if(!ok) {
return {ok, children, calls} return {ok, children, calls}
} }
@@ -914,7 +926,7 @@ const eval_binary_expr = (node, scope, callsleft) => {
} }
const do_eval_frame_expr = (node, scope, callsleft) => { const do_eval_frame_expr = (node, scope, callsleft, context) => {
if([ if([
'identifier', 'identifier',
'builtin_identifier', 'builtin_identifier',
@@ -930,9 +942,9 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
'key_value_pair', 'key_value_pair',
'computed_property' 'computed_property'
].includes(node.type)) { ].includes(node.type)) {
return eval_children(node, scope, callsleft) return eval_children(node, scope, callsleft, context)
} else if(node.type == 'array_literal' || node.type == 'call_args'){ } else if(node.type == 'array_literal' || node.type == 'call_args'){
const {ok, children, calls} = eval_children(node, scope, callsleft) const {ok, children, calls} = eval_children(node, scope, callsleft, context)
if(!ok) { if(!ok) {
return {ok, children, calls} return {ok, children, calls}
} }
@@ -948,7 +960,7 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
) )
return {ok, children, calls, value} return {ok, children, calls, value}
} else if(node.type == 'object_literal'){ } else if(node.type == 'object_literal'){
const {ok, children, calls} = eval_children(node, scope, callsleft) const {ok, children, calls} = eval_children(node, scope, callsleft, context)
if(!ok) { if(!ok) {
return {ok, children, calls} return {ok, children, calls}
} }
@@ -979,16 +991,14 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
) )
return {ok, children, value, calls} return {ok, children, value, calls}
} else if(node.type == 'function_call' || node.type == 'new'){ } else if(node.type == 'function_call' || node.type == 'new'){
const {ok, children, calls} = eval_children(node, scope, callsleft) const {ok, children, calls} = eval_children(node, scope, callsleft, context)
if(!ok) { if(!ok) {
return {ok: false, children, calls} return {ok: false, children, calls}
} else { } else {
if(typeof(children[0].result.value) != 'function') { if(typeof(children[0].result.value) != 'function') {
return { return {
ok: false, ok: false,
// TODO pass calltree_node here and extract error error: context.calltree_node.error,
// TODO fix error messages
error: new Error('is not a function'),
children, children,
calls, calls,
} }
@@ -1024,7 +1034,8 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
const {node: cond_evaled, calls: calls_after_cond} = eval_frame_expr( const {node: cond_evaled, calls: calls_after_cond} = eval_frame_expr(
node.cond, node.cond,
scope, scope,
callsleft callsleft,
context
) )
const {ok, value} = cond_evaled.result const {ok, value} = cond_evaled.result
const branches = node.branches const branches = node.branches
@@ -1038,7 +1049,8 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
const {node: branch_evaled, calls: calls_after_branch} = eval_frame_expr( const {node: branch_evaled, calls: calls_after_branch} = eval_frame_expr(
branches[value ? 0 : 1], branches[value ? 0 : 1],
scope, scope,
calls_after_cond calls_after_cond,
context
) )
const children = value const children = value
? [cond_evaled, branch_evaled, branches[1]] ? [cond_evaled, branch_evaled, branches[1]]
@@ -1051,7 +1063,7 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
} }
} }
} else if(node.type == 'member_access'){ } else if(node.type == 'member_access'){
const {ok, children, calls} = eval_children(node, scope, callsleft) const {ok, children, calls} = eval_children(node, scope, callsleft, context)
if(!ok) { if(!ok) {
return {ok: false, children, calls} return {ok: false, children, calls}
} }
@@ -1071,7 +1083,7 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
} }
} else if(node.type == 'unary') { } else if(node.type == 'unary') {
const {ok, children, calls} = eval_children(node, scope, callsleft) const {ok, children, calls} = eval_children(node, scope, callsleft, context)
if(!ok) { if(!ok) {
return {ok: false, children, calls} return {ok: false, children, calls}
} else { } else {
@@ -1108,13 +1120,14 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
} }
} else if(node.type == 'binary' && !['&&', '||', '??'].includes(node.operator)){ } else if(node.type == 'binary' && !['&&', '||', '??'].includes(node.operator)){
return eval_binary_expr(node, scope, callsleft) return eval_binary_expr(node, scope, callsleft, context)
} else if(node.type == 'binary' && ['&&', '||', '??'].includes(node.operator)){ } else if(node.type == 'binary' && ['&&', '||', '??'].includes(node.operator)){
const {node: left_evaled, calls} = eval_frame_expr( const {node: left_evaled, calls} = eval_frame_expr(
node.children[0], node.children[0],
scope, scope,
callsleft callsleft,
context
) )
const {ok, value} = left_evaled.result const {ok, value} = left_evaled.result
@@ -1134,11 +1147,11 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
calls, calls,
} }
} else { } else {
return eval_binary_expr(node, scope, callsleft) return eval_binary_expr(node, scope, callsleft, context)
} }
} else if(node.type == 'grouping'){ } else if(node.type == 'grouping'){
const {ok, children, calls} = eval_children(node, scope, callsleft) const {ok, children, calls} = eval_children(node, scope, callsleft, context)
if(!ok) { if(!ok) {
return {ok, children, calls} return {ok, children, calls}
} else { } else {
@@ -1150,7 +1163,7 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
} }
} }
const eval_children = (node, scope, calls) => { const eval_children = (node, scope, calls, context) => {
return node.children.reduce( return node.children.reduce(
({ok, children, calls}, child) => { ({ok, children, calls}, child) => {
let next_child, next_ok, next_calls let next_child, next_ok, next_calls
@@ -1159,7 +1172,7 @@ const eval_children = (node, scope, calls) => {
next_ok = false; next_ok = false;
next_calls = calls; next_calls = calls;
} else { } else {
const result = eval_frame_expr(child, scope, calls); const result = eval_frame_expr(child, scope, calls, context)
next_child = result.node; next_child = result.node;
next_calls = result.calls; next_calls = result.calls;
next_ok = next_child.result.ok; next_ok = next_child.result.ok;
@@ -1170,8 +1183,9 @@ const eval_children = (node, scope, calls) => {
) )
} }
const eval_frame_expr = (node, scope, callsleft) => { const eval_frame_expr = (node, scope, callsleft, context) => {
const {ok, error, value, call, children, calls} = do_eval_frame_expr(node, scope, callsleft) const {ok, error, value, call, children, calls}
= do_eval_frame_expr(node, scope, callsleft, context)
if(callsleft != null && calls == null) { if(callsleft != null && calls == null) {
// TODO remove it, just for debug // TODO remove it, just for debug
console.error('node', node) console.error('node', node)
@@ -1234,7 +1248,7 @@ const apply_assignments = (do_node, assignments) => {
} }
const eval_statement = (s, scope, calls, modules) => { const eval_statement = (s, scope, calls, context) => {
if(s.type == 'do') { if(s.type == 'do') {
const node = s const node = s
const {ok, assignments, returned, stmts, calls: nextcalls} = node.stmts.reduce( const {ok, assignments, returned, stmts, calls: nextcalls} = node.stmts.reduce(
@@ -1249,7 +1263,7 @@ const eval_statement = (s, scope, calls, modules) => {
assignments: next_assignments, assignments: next_assignments,
scope: nextscope, scope: nextscope,
calls: next_calls, calls: next_calls,
} = eval_statement(s, scope, calls, modules) } = eval_statement(s, scope, calls, context)
return { return {
ok, ok,
returned, returned,
@@ -1275,7 +1289,8 @@ const eval_statement = (s, scope, calls, modules) => {
} else if(s.type == 'const' || s.type == 'assignment') { } else if(s.type == 'const' || s.type == 'assignment') {
// TODO default values for destructuring can be function calls // TODO default values for destructuring can be function calls
const {node, calls: next_calls} = eval_frame_expr(s.expr, scope, calls) const {node, calls: next_calls}
= eval_frame_expr(s.expr, scope, calls, context)
const s_expr_evaled = {...s, children: [s.name_node, node]} const s_expr_evaled = {...s, children: [s.name_node, node]}
if(!node.result.ok) { if(!node.result.ok) {
return { return {
@@ -1352,7 +1367,8 @@ const eval_statement = (s, scope, calls, modules) => {
} }
} else if(s.type == 'return') { } else if(s.type == 'return') {
const {node, calls: next_calls} = eval_frame_expr(s.expr, scope, calls) const {node, calls: next_calls} =
eval_frame_expr(s.expr, scope, calls, context)
return { return {
ok: node.result.ok, ok: node.result.ok,
@@ -1363,7 +1379,8 @@ const eval_statement = (s, scope, calls, modules) => {
} }
} else if(s.type == 'export') { } else if(s.type == 'export') {
const {ok, scope: nextscope, calls: next_calls, node} = eval_statement(s.binding, scope, calls) const {ok, scope: nextscope, calls: next_calls, node}
= eval_statement(s.binding, scope, calls, context)
return { return {
ok, ok,
scope: nextscope, scope: nextscope,
@@ -1373,7 +1390,7 @@ const eval_statement = (s, scope, calls, modules) => {
} else if(s.type == 'import') { } else if(s.type == 'import') {
const children = s.imports.map(i => ( const children = s.imports.map(i => (
{...i, {...i,
result: {ok: true, value: modules[s.full_import_path][i.value]} result: {ok: true, value: context.modules[s.full_import_path][i.value]}
} }
)) ))
const imported_scope = Object.fromEntries(children.map(i => [i.value, i.result.value])) const imported_scope = Object.fromEntries(children.map(i => [i.value, i.result.value]))
@@ -1385,7 +1402,7 @@ const eval_statement = (s, scope, calls, modules) => {
} }
} else if(s.type == 'if') { } else if(s.type == 'if') {
const {node, calls: next_calls} = eval_frame_expr(s.cond, scope, calls) const {node, calls: next_calls} = eval_frame_expr(s.cond, scope, calls, context)
if(!node.result.ok) { if(!node.result.ok) {
return { return {
@@ -1410,6 +1427,7 @@ const eval_statement = (s, scope, calls, modules) => {
s.branches[0], s.branches[0],
scope, scope,
next_calls, next_calls,
context
) )
return { return {
ok: evaled_branch.result.ok, ok: evaled_branch.result.ok,
@@ -1445,6 +1463,7 @@ const eval_statement = (s, scope, calls, modules) => {
active_branch, active_branch,
scope, scope,
next_calls, next_calls,
context,
) )
const children = node.result.value const children = node.result.value
@@ -1467,7 +1486,7 @@ const eval_statement = (s, scope, calls, modules) => {
} else if(s.type == 'throw') { } else if(s.type == 'throw') {
const {node, calls: next_calls} = eval_frame_expr(s.expr, scope, calls) const {node, calls: next_calls} = eval_frame_expr(s.expr, scope, calls, context)
return { return {
ok: false, ok: false,
@@ -1484,11 +1503,7 @@ const eval_statement = (s, scope, calls, modules) => {
} else { } else {
// stmt type is expression // stmt type is expression
const {node, calls: next_calls} = eval_frame_expr( const {node, calls: next_calls} = eval_frame_expr(s, scope, calls, context)
s,
scope,
calls,
)
return { return {
ok: node.result.ok, ok: node.result.ok,
node, node,
@@ -1503,12 +1518,13 @@ export const eval_frame = (calltree_node, modules) => {
throw new Error('illegal state') throw new Error('illegal state')
} }
const node = calltree_node.code const node = calltree_node.code
const context = {calltree_node, modules}
if(node.type == 'do') { if(node.type == 'do') {
return eval_statement( return eval_statement(
node, node,
{}, {},
calltree_node.children, calltree_node.children,
modules, context,
).node ).node
} else { } else {
// TODO default values for destructuring can be function calls // TODO default values for destructuring can be function calls
@@ -1558,9 +1574,10 @@ export const eval_frame = (calltree_node, modules) => {
body, body,
scope, scope,
calltree_node.children, calltree_node.children,
context,
).node ).node
} else { } else {
nextbody = eval_frame_expr(body, scope, calltree_node.children) nextbody = eval_frame_expr(body, scope, calltree_node.children, context)
.node .node
} }

View File

@@ -462,22 +462,20 @@ export const tests = [
) )
}), }),
test('undefined is not a function', () => { test('error is not a function', () => {
const code = assert_code_error(
` `
const x = () => null(); const x = null
const unreachable = () => 1 x()
x(); `,
'TypeError: x is not a function'
)
assert_code_error(
` `
const s1 = test_initial_state(code) const foo = () => ([{bar: {}}])
// TODO fix error messages foo()[0].bar.baz()
const message = root_calltree_node(s1).error.message `,
assert_equal( 'TypeError: foo(...)[0].bar.baz is not a function'
message == "Cannot read property 'apply' of null"
||
message == "Cannot read properties of null (reading 'apply')"
,
true
) )
}), }),