Skip to content

Optional chaining fixes #533

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion quickjs-opcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in pha
def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */
def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */
def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */

def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */
def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */
def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */

def( source_loc, 9, 0, 0, u32x2) /* emitted in phase 1, removed in phase 3 */
Expand Down
116 changes: 109 additions & 7 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -20254,6 +20254,14 @@ static int new_label(JSParseState *s)
return new_label_fd(s->cur_func, -1);
}

/* don't update the last opcode and don't emit line number info */
static void emit_label_raw(JSParseState *s, int label)
{
emit_u8(s, OP_label);
emit_u32(s, label);
s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size;
}

/* return the label ID offset */
static int emit_label(JSParseState *s, int label)
{
Expand Down Expand Up @@ -23385,6 +23393,25 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2;
drop_count = 2;
break;
case OP_get_field_opt_chain:
{
int opt_chain_label, next_label;
opt_chain_label = get_u32(fd->byte_code.buf +
fd->last_opcode_pos + 1 + 4 + 1);
/* keep the object on the stack */
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2;
fd->byte_code.size = fd->last_opcode_pos + 1 + 4;
next_label = emit_goto(s, OP_goto, -1);
emit_label(s, opt_chain_label);
/* need an additional undefined value for the
case where the optional field does not
exists */
emit_op(s, OP_undefined);
emit_label(s, next_label);
drop_count = 2;
opcode = OP_get_field;
}
break;
case OP_scope_get_private_field:
/* keep the object on the stack */
fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_private_field2;
Expand All @@ -23395,6 +23422,25 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2;
drop_count = 2;
break;
case OP_get_array_el_opt_chain:
{
int opt_chain_label, next_label;
opt_chain_label = get_u32(fd->byte_code.buf +
fd->last_opcode_pos + 1 + 1);
/* keep the object on the stack */
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2;
fd->byte_code.size = fd->last_opcode_pos + 1;
next_label = emit_goto(s, OP_goto, -1);
emit_label(s, opt_chain_label);
/* need an additional undefined value for the
case where the optional field does not
exists */
emit_op(s, OP_undefined);
emit_label(s, next_label);
drop_count = 2;
opcode = OP_get_array_el;
}
break;
case OP_scope_get_var:
{
JSAtom name;
Expand Down Expand Up @@ -23653,8 +23699,23 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
break;
}
}
if (optional_chaining_label >= 0)
emit_label(s, optional_chaining_label);
if (optional_chaining_label >= 0) {
JSFunctionDef *fd = s->cur_func;
int opcode;
emit_label_raw(s, optional_chaining_label);
/* modify the last opcode so that it is an indicator of an
optional chain */
opcode = get_prev_opcode(fd);
if (opcode == OP_get_field || opcode == OP_get_array_el) {
if (opcode == OP_get_field)
opcode = OP_get_field_opt_chain;
else
opcode = OP_get_array_el_opt_chain;
fd->byte_code.buf[fd->last_opcode_pos] = opcode;
} else {
fd->last_opcode_pos = -1;
}
}
return 0;
}

Expand All @@ -23670,27 +23731,57 @@ static __exception int js_parse_delete(JSParseState *s)
return -1;
switch(opcode = get_prev_opcode(fd)) {
case OP_get_field:
case OP_get_field_opt_chain:
{
JSValue val;
int ret;

int ret, opt_chain_label, next_label;
if (opcode == OP_get_field_opt_chain) {
opt_chain_label = get_u32(fd->byte_code.buf +
fd->last_opcode_pos + 1 + 4 + 1);
} else {
opt_chain_label = -1;
}
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
fd->byte_code.size = fd->last_opcode_pos;
fd->last_opcode_pos = -1;
val = JS_AtomToValue(s->ctx, name);
ret = emit_push_const(s, val, 1);
JS_FreeValue(s->ctx, val);
JS_FreeAtom(s->ctx, name);
if (ret)
return ret;
emit_op(s, OP_delete);
if (opt_chain_label >= 0) {
next_label = emit_goto(s, OP_goto, -1);
emit_label(s, opt_chain_label);
/* if the optional chain is not taken, return 'true' */
emit_op(s, OP_drop);
emit_op(s, OP_push_true);
emit_label(s, next_label);
}
fd->last_opcode_pos = -1;
}
goto do_delete;
break;
case OP_get_array_el:
fd->byte_code.size = fd->last_opcode_pos;
fd->last_opcode_pos = -1;
do_delete:
emit_op(s, OP_delete);
break;
case OP_get_array_el_opt_chain:
{
int opt_chain_label, next_label;
opt_chain_label = get_u32(fd->byte_code.buf +
fd->last_opcode_pos + 1 + 1);
fd->byte_code.size = fd->last_opcode_pos;
emit_op(s, OP_delete);
next_label = emit_goto(s, OP_goto, -1);
emit_label(s, opt_chain_label);
/* if the optional chain is not taken, return 'true' */
emit_op(s, OP_drop);
emit_op(s, OP_push_true);
emit_label(s, next_label);
fd->last_opcode_pos = -1;
}
break;
case OP_scope_get_var:
/* 'delete this': this is not a reference */
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
Expand Down Expand Up @@ -30371,6 +30462,17 @@ static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s)
/* only used during parsing */
break;

case OP_get_field_opt_chain: /* equivalent to OP_get_field */
{
JSAtom name = get_u32(bc_buf + pos + 1);
dbuf_putc(&bc_out, OP_get_field);
dbuf_put_u32(&bc_out, name);
}
break;
case OP_get_array_el_opt_chain: /* equivalent to OP_get_array_el */
dbuf_putc(&bc_out, OP_get_array_el);
break;

default:
no_change:
dbuf_put(&bc_out, bc_buf + pos, len);
Expand Down
2 changes: 0 additions & 2 deletions test262_errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,6 @@ test262/test/language/expressions/generators/static-init-await-binding.js:16: Sy
test262/test/language/expressions/generators/static-init-await-binding.js:16: strict mode: SyntaxError: 'await' is a reserved identifier
test262/test/language/expressions/member-expression/computed-reference-null-or-undefined.js:28: Test262Error: Expected a TypeError but got a Test262Error
test262/test/language/expressions/member-expression/computed-reference-null-or-undefined.js:28: strict mode: Test262Error: Expected a TypeError but got a Test262Error
test262/test/language/expressions/optional-chaining/optional-call-preserves-this.js:21: TypeError: cannot read property 'c' of undefined
test262/test/language/expressions/optional-chaining/optional-call-preserves-this.js:16: strict mode: TypeError: cannot read property '_b' of undefined
test262/test/language/module-code/top-level-await/async-module-does-not-block-sibling-modules.js:13: SyntaxError: Could not find export 'check' in module 'test262/test/language/module-code/top-level-await/async-module-sync_FIXTURE.js'
test262/test/staging/set-is-subset-on-set-like.js:24: TypeError: not a function
test262/test/staging/set-is-subset-on-set-like.js:24: strict mode: TypeError: not a function
Expand Down
26 changes: 26 additions & 0 deletions tests/test_language.js
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,31 @@ function test_syntax()
assert_throws(SyntaxError, "if \\u0123");
}

/* optional chaining tests not present in test262 */
function test_optional_chaining()
{
var a, z;
z = null;
a = { b: { c: 2 } };
assert(delete z?.b.c, true);
assert(delete a?.b.c, true);
assert(JSON.stringify(a), '{"b":{}}', "optional chaining delete");

a = { b: { c: 2 } };
assert(delete z?.b["c"], true);
assert(delete a?.b["c"], true);
assert(JSON.stringify(a), '{"b":{}}');

a = {
b() { return this._b; },
_b: { c: 42 }
};

assert((a?.b)().c, 42);

assert((a?.["b"])().c, 42);
}

test_op1();
test_cvt();
test_eq();
Expand All @@ -688,3 +713,4 @@ test_function_expr_name();
test_reserved_names();
test_number_literals();
test_syntax();
test_optional_chaining();
Loading