Skip to content

Commit ac958f1

Browse files
committed
Optional chaining fixes
Ref: bellard/quickjs@f25e5d4
1 parent 0990875 commit ac958f1

File tree

4 files changed

+137
-10
lines changed

4 files changed

+137
-10
lines changed

quickjs-opcode.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,8 @@ def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in pha
282282
def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */
283283
def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */
284284
def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */
285-
285+
def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */
286+
def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */
286287
def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */
287288

288289
def( source_loc, 9, 0, 0, u32x2) /* emitted in phase 1, removed in phase 3 */

quickjs.c

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20254,6 +20254,14 @@ static int new_label(JSParseState *s)
2025420254
return new_label_fd(s->cur_func, -1);
2025520255
}
2025620256

20257+
/* don't update the last opcode and don't emit line number info */
20258+
static void emit_label_raw(JSParseState *s, int label)
20259+
{
20260+
emit_u8(s, OP_label);
20261+
emit_u32(s, label);
20262+
s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size;
20263+
}
20264+
2025720265
/* return the label ID offset */
2025820266
static int emit_label(JSParseState *s, int label)
2025920267
{
@@ -23385,6 +23393,25 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
2338523393
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2;
2338623394
drop_count = 2;
2338723395
break;
23396+
case OP_get_field_opt_chain:
23397+
{
23398+
int opt_chain_label, next_label;
23399+
opt_chain_label = get_u32(fd->byte_code.buf +
23400+
fd->last_opcode_pos + 1 + 4 + 1);
23401+
/* keep the object on the stack */
23402+
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2;
23403+
fd->byte_code.size = fd->last_opcode_pos + 1 + 4;
23404+
next_label = emit_goto(s, OP_goto, -1);
23405+
emit_label(s, opt_chain_label);
23406+
/* need an additional undefined value for the
23407+
case where the optional field does not
23408+
exists */
23409+
emit_op(s, OP_undefined);
23410+
emit_label(s, next_label);
23411+
drop_count = 2;
23412+
opcode = OP_get_field;
23413+
}
23414+
break;
2338823415
case OP_scope_get_private_field:
2338923416
/* keep the object on the stack */
2339023417
fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_private_field2;
@@ -23395,6 +23422,25 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
2339523422
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2;
2339623423
drop_count = 2;
2339723424
break;
23425+
case OP_get_array_el_opt_chain:
23426+
{
23427+
int opt_chain_label, next_label;
23428+
opt_chain_label = get_u32(fd->byte_code.buf +
23429+
fd->last_opcode_pos + 1 + 1);
23430+
/* keep the object on the stack */
23431+
fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2;
23432+
fd->byte_code.size = fd->last_opcode_pos + 1;
23433+
next_label = emit_goto(s, OP_goto, -1);
23434+
emit_label(s, opt_chain_label);
23435+
/* need an additional undefined value for the
23436+
case where the optional field does not
23437+
exists */
23438+
emit_op(s, OP_undefined);
23439+
emit_label(s, next_label);
23440+
drop_count = 2;
23441+
opcode = OP_get_array_el;
23442+
}
23443+
break;
2339823444
case OP_scope_get_var:
2339923445
{
2340023446
JSAtom name;
@@ -23653,8 +23699,23 @@ static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags)
2365323699
break;
2365423700
}
2365523701
}
23656-
if (optional_chaining_label >= 0)
23657-
emit_label(s, optional_chaining_label);
23702+
if (optional_chaining_label >= 0) {
23703+
JSFunctionDef *fd = s->cur_func;
23704+
int opcode;
23705+
emit_label_raw(s, optional_chaining_label);
23706+
/* modify the last opcode so that it is an indicator of an
23707+
optional chain */
23708+
opcode = get_prev_opcode(fd);
23709+
if (opcode == OP_get_field || opcode == OP_get_array_el) {
23710+
if (opcode == OP_get_field)
23711+
opcode = OP_get_field_opt_chain;
23712+
else
23713+
opcode = OP_get_array_el_opt_chain;
23714+
fd->byte_code.buf[fd->last_opcode_pos] = opcode;
23715+
} else {
23716+
fd->last_opcode_pos = -1;
23717+
}
23718+
}
2365823719
return 0;
2365923720
}
2366023721

@@ -23670,27 +23731,57 @@ static __exception int js_parse_delete(JSParseState *s)
2367023731
return -1;
2367123732
switch(opcode = get_prev_opcode(fd)) {
2367223733
case OP_get_field:
23734+
case OP_get_field_opt_chain:
2367323735
{
2367423736
JSValue val;
23675-
int ret;
23676-
23737+
int ret, opt_chain_label, next_label;
23738+
if (opcode == OP_get_field_opt_chain) {
23739+
opt_chain_label = get_u32(fd->byte_code.buf +
23740+
fd->last_opcode_pos + 1 + 4 + 1);
23741+
} else {
23742+
opt_chain_label = -1;
23743+
}
2367723744
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
2367823745
fd->byte_code.size = fd->last_opcode_pos;
23679-
fd->last_opcode_pos = -1;
2368023746
val = JS_AtomToValue(s->ctx, name);
2368123747
ret = emit_push_const(s, val, 1);
2368223748
JS_FreeValue(s->ctx, val);
2368323749
JS_FreeAtom(s->ctx, name);
2368423750
if (ret)
2368523751
return ret;
23752+
emit_op(s, OP_delete);
23753+
if (opt_chain_label >= 0) {
23754+
next_label = emit_goto(s, OP_goto, -1);
23755+
emit_label(s, opt_chain_label);
23756+
/* if the optional chain is not taken, return 'true' */
23757+
emit_op(s, OP_drop);
23758+
emit_op(s, OP_push_true);
23759+
emit_label(s, next_label);
23760+
}
23761+
fd->last_opcode_pos = -1;
2368623762
}
23687-
goto do_delete;
23763+
break;
2368823764
case OP_get_array_el:
2368923765
fd->byte_code.size = fd->last_opcode_pos;
2369023766
fd->last_opcode_pos = -1;
23691-
do_delete:
2369223767
emit_op(s, OP_delete);
2369323768
break;
23769+
case OP_get_array_el_opt_chain:
23770+
{
23771+
int opt_chain_label, next_label;
23772+
opt_chain_label = get_u32(fd->byte_code.buf +
23773+
fd->last_opcode_pos + 1 + 1);
23774+
fd->byte_code.size = fd->last_opcode_pos;
23775+
emit_op(s, OP_delete);
23776+
next_label = emit_goto(s, OP_goto, -1);
23777+
emit_label(s, opt_chain_label);
23778+
/* if the optional chain is not taken, return 'true' */
23779+
emit_op(s, OP_drop);
23780+
emit_op(s, OP_push_true);
23781+
emit_label(s, next_label);
23782+
fd->last_opcode_pos = -1;
23783+
}
23784+
break;
2369423785
case OP_scope_get_var:
2369523786
/* 'delete this': this is not a reference */
2369623787
name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1);
@@ -30371,6 +30462,17 @@ static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s)
3037130462
/* only used during parsing */
3037230463
break;
3037330464

30465+
case OP_get_field_opt_chain: /* equivalent to OP_get_field */
30466+
{
30467+
JSAtom name = get_u32(bc_buf + pos + 1);
30468+
dbuf_putc(&bc_out, OP_get_field);
30469+
dbuf_put_u32(&bc_out, name);
30470+
}
30471+
break;
30472+
case OP_get_array_el_opt_chain: /* equivalent to OP_get_array_el */
30473+
dbuf_putc(&bc_out, OP_get_array_el);
30474+
break;
30475+
3037430476
default:
3037530477
no_change:
3037630478
dbuf_put(&bc_out, bc_buf + pos, len);

test262_errors.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,5 @@ test262/test/language/expressions/generators/static-init-await-binding.js:16: Sy
134134
test262/test/language/expressions/generators/static-init-await-binding.js:16: strict mode: SyntaxError: 'await' is a reserved identifier
135135
test262/test/language/expressions/member-expression/computed-reference-null-or-undefined.js:28: Test262Error: Expected a TypeError but got a Test262Error
136136
test262/test/language/expressions/member-expression/computed-reference-null-or-undefined.js:28: strict mode: Test262Error: Expected a TypeError but got a Test262Error
137-
test262/test/language/expressions/optional-chaining/optional-call-preserves-this.js:21: TypeError: cannot read property 'c' of undefined
138-
test262/test/language/expressions/optional-chaining/optional-call-preserves-this.js:16: strict mode: TypeError: cannot read property '_b' of undefined
139137
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'
140138
test262/test/staging/top-level-await/tla-hang-entry.js:10: TypeError: $DONE() not called

tests/test_language.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,31 @@ function test_syntax()
666666
assert_throws(SyntaxError, "if \\u0123");
667667
}
668668

669+
/* optional chaining tests not present in test262 */
670+
function test_optional_chaining()
671+
{
672+
var a, z;
673+
z = null;
674+
a = { b: { c: 2 } };
675+
assert(delete z?.b.c, true);
676+
assert(delete a?.b.c, true);
677+
assert(JSON.stringify(a), '{"b":{}}', "optional chaining delete");
678+
679+
a = { b: { c: 2 } };
680+
assert(delete z?.b["c"], true);
681+
assert(delete a?.b["c"], true);
682+
assert(JSON.stringify(a), '{"b":{}}');
683+
684+
a = {
685+
b() { return this._b; },
686+
_b: { c: 42 }
687+
};
688+
689+
assert((a?.b)().c, 42);
690+
691+
assert((a?.["b"])().c, 42);
692+
}
693+
669694
test_op1();
670695
test_cvt();
671696
test_eq();
@@ -688,3 +713,4 @@ test_function_expr_name();
688713
test_reserved_names();
689714
test_number_literals();
690715
test_syntax();
716+
test_optional_chaining();

0 commit comments

Comments
 (0)