Skip to content

Commit 2df4cac

Browse files
committed
fix: parser disambiguation for 'for (await using of of [])' - of is always a binding name in await-using context
1 parent 520a293 commit 2df4cac

8 files changed

Lines changed: 928 additions & 92 deletions

File tree

src/core/eval.rs

Lines changed: 567 additions & 66 deletions
Large diffs are not rendered by default.

src/core/js_error.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ fn initialize_suppressed_error<'gc>(
257257
}
258258

259259
/// Create a SuppressedError instance: new SuppressedError(error, suppressed, message)
260+
/// Per spec, own properties must appear in order: message, error, suppressed.
260261
pub fn create_suppressed_error<'gc>(
261262
mc: &MutationContext<'gc>,
262263
env: &JSObjectDataPtr<'gc>,
@@ -291,20 +292,43 @@ pub fn create_suppressed_error<'gc>(
291292
None
292293
};
293294

294-
let err_obj_val = create_error(mc, proto, message_value).map_err(EvalError::from)?;
295-
let err_obj = match &err_obj_val {
296-
Value::Object(o) => *o,
297-
_ => return Ok(err_obj_val),
295+
// Build the error object manually to ensure correct property order:
296+
// message, error, suppressed (then stack, constructor).
297+
let err_obj = new_js_object_data(mc);
298+
err_obj.borrow_mut(mc).prototype = proto;
299+
300+
// 1. message — only set if not undefined
301+
let msg_str = match &message_value {
302+
Value::Undefined => String::new(),
303+
Value::String(s) => {
304+
object_set_key_value(mc, &err_obj, "message", &Value::String(s.clone())).map_err(EvalError::from)?;
305+
err_obj.borrow_mut(mc).set_non_enumerable("message");
306+
utf16_to_utf8(s)
307+
}
308+
other => {
309+
let s = utf8_to_utf16(&value_to_string(other));
310+
object_set_key_value(mc, &err_obj, "message", &Value::String(s.clone())).map_err(EvalError::from)?;
311+
err_obj.borrow_mut(mc).set_non_enumerable("message");
312+
utf16_to_utf8(&s)
313+
}
298314
};
299315

300-
// Set .error property (writable, enumerable, configurable — per spec non-enumerable actually)
316+
// 2. error
301317
object_set_key_value(mc, &err_obj, "error", &error).map_err(EvalError::from)?;
302318
err_obj.borrow_mut(mc).set_non_enumerable("error");
303319

304-
// Set .suppressed property
320+
// 3. suppressed
305321
object_set_key_value(mc, &err_obj, "suppressed", &suppressed).map_err(EvalError::from)?;
306322
err_obj.borrow_mut(mc).set_non_enumerable("suppressed");
307323

324+
// 4. stack (implementation-defined, after the spec-required properties)
325+
let stack_str = format!("SuppressedError: {msg_str}");
326+
object_set_key_value(mc, &err_obj, "stack", &Value::String(utf8_to_utf16(&stack_str))).map_err(EvalError::from)?;
327+
err_obj.borrow_mut(mc).set_non_enumerable("stack");
328+
329+
// Internal marker
330+
slot_set(mc, &err_obj, InternalSlot::IsError, &Value::Boolean(true));
331+
308332
Ok(Value::Object(err_obj))
309333
}
310334

src/core/parser.rs

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,212 @@ fn parse_for_statement(t: &[TokenData], index: &mut usize) -> Result<Statement,
270270
*index += 1;
271271
}
272272

273+
// Check for `using` or `await using` in the for-head
274+
// `for (using x = expr; ...; ...)` or `for (using x of iterable)` or
275+
// `for (await using x of iterable)` or `for (await using x = expr; ...; ...)`
276+
{
277+
// Detect `await using` inside the parens: `for (await using ...)`
278+
// In this case, `await` is NOT the `for await` syntax but an `await using` declaration.
279+
let is_await_using_in_parens = matches!(t[*index].token, Token::Await) && {
280+
let mut pk = *index + 1;
281+
while pk < t.len() && matches!(t[pk].token, Token::LineTerminator) {
282+
pk += 1;
283+
}
284+
matches!(&t[pk].token, Token::Identifier(n) if n == "using")
285+
};
286+
if is_await_using_in_parens {
287+
// Consume `await`; the `using` handling below will pick up the rest.
288+
// Mark this as an await-using declaration context.
289+
*index += 1;
290+
while *index < t.len() && matches!(t[*index].token, Token::LineTerminator) {
291+
*index += 1;
292+
}
293+
is_for_await = true;
294+
}
295+
296+
let is_using_kw = matches!(&t[*index].token, Token::Identifier(n) if n == "using");
297+
if is_using_kw {
298+
// Peek ahead: `using` followed by an identifier means a using declaration
299+
let mut peek = *index + 1;
300+
while peek < t.len() && matches!(t[peek].token, Token::LineTerminator) {
301+
peek += 1;
302+
}
303+
let next_is_ident = peek < t.len() && matches!(&t[peek].token, Token::Identifier(_));
304+
305+
// Special case: `for (using of ...` where `of` is the identifier after `using`.
306+
// Per spec, `using of` is only a using-declaration if followed by `=`
307+
// (i.e., `for (using of = expr; ...; ...)`). Otherwise, `using` is just
308+
// an identifier and `of` is the for-of keyword.
309+
let is_using_of = next_is_ident && matches!(&t[peek].token, Token::Identifier(n) if n == "of");
310+
let using_of_is_decl = if is_using_of {
311+
let mut peek2 = peek + 1;
312+
while peek2 < t.len() && matches!(t[peek2].token, Token::LineTerminator) {
313+
peek2 += 1;
314+
}
315+
peek2 < t.len() && matches!(t[peek2].token, Token::Assign)
316+
} else {
317+
false
318+
};
319+
320+
let enter_using_path = next_is_ident && (is_await_using_in_parens || !is_using_of || using_of_is_decl);
321+
322+
if enter_using_path {
323+
*index += 1; // consume `using`
324+
// Skip line terminators
325+
while *index < t.len() && matches!(t[*index].token, Token::LineTerminator) {
326+
*index += 1;
327+
}
328+
// Parse the first binding name
329+
let first_name = match &t[*index].token {
330+
Token::Identifier(n) => n.clone(),
331+
_ => return Err(raise_parse_error_at!(t.get(*index))),
332+
};
333+
*index += 1;
334+
while *index < t.len() && matches!(t[*index].token, Token::LineTerminator) {
335+
*index += 1;
336+
}
337+
338+
// Check if followed by `of` (for-of/for-await-of with using)
339+
if *index < t.len() && matches!(&t[*index].token, Token::Identifier(n) if n == "of") {
340+
*index += 1; // consume `of`
341+
let iterable = parse_assignment(t, index)?;
342+
while *index < t.len() && matches!(t[*index].token, Token::LineTerminator) {
343+
*index += 1;
344+
}
345+
if !matches!(t[*index].token, Token::RParen) {
346+
return Err(raise_parse_error_at!(t.get(*index)));
347+
}
348+
*index += 1; // consume )
349+
while *index < t.len() && matches!(t[*index].token, Token::LineTerminator) {
350+
*index += 1;
351+
}
352+
let body = parse_statement_item(t, index)?;
353+
let body_stmts = match *body.kind {
354+
StatementKind::Block(b) => b,
355+
_ => vec![body],
356+
};
357+
let kind = if is_for_await {
358+
StatementKind::ForAwaitOf(Some(crate::core::VarDeclKind::AwaitUsing), first_name, iterable, body_stmts)
359+
} else {
360+
StatementKind::ForOf(Some(crate::core::VarDeclKind::Using), first_name, iterable, body_stmts)
361+
};
362+
return Ok(Statement {
363+
kind: Box::new(kind),
364+
line,
365+
column,
366+
});
367+
}
368+
369+
// C-style for with using: `for (using x = expr, ...; test; update) body`
370+
if !matches!(t[*index].token, Token::Assign) {
371+
return Err(raise_parse_error!("using declarations must have an initializer", line, column));
372+
}
373+
*index += 1; // consume `=`
374+
let first_init = parse_assignment(t, index)?;
375+
let mut using_decls = vec![(first_name, first_init)];
376+
377+
// Check for comma-separated additional declarations
378+
while *index < t.len() && matches!(t[*index].token, Token::Comma) {
379+
*index += 1;
380+
while *index < t.len() && matches!(t[*index].token, Token::LineTerminator) {
381+
*index += 1;
382+
}
383+
let next_name = match &t[*index].token {
384+
Token::Identifier(n) => n.clone(),
385+
_ => return Err(raise_parse_error_at!(t.get(*index))),
386+
};
387+
*index += 1;
388+
if !matches!(t[*index].token, Token::Assign) {
389+
return Err(raise_parse_error!("using declarations must have an initializer", line, column));
390+
}
391+
*index += 1; // consume `=`
392+
let next_init = parse_assignment(t, index)?;
393+
using_decls.push((next_name, next_init));
394+
}
395+
396+
// Skip line terminators before the first semicolon
397+
while *index < t.len() && matches!(t[*index].token, Token::LineTerminator) {
398+
*index += 1;
399+
}
400+
if !matches!(t[*index].token, Token::Semicolon) {
401+
return Err(raise_parse_error_at!(t.get(*index)));
402+
}
403+
*index += 1; // consume ;
404+
405+
// Parse test expression
406+
while *index < t.len() && matches!(t[*index].token, Token::LineTerminator) {
407+
*index += 1;
408+
}
409+
let test = if !matches!(t[*index].token, Token::Semicolon) {
410+
Some(parse_expression(t, index)?)
411+
} else {
412+
None
413+
};
414+
while *index < t.len() && matches!(t[*index].token, Token::LineTerminator) {
415+
*index += 1;
416+
}
417+
if !matches!(t[*index].token, Token::Semicolon) {
418+
return Err(raise_parse_error_at!(t.get(*index)));
419+
}
420+
*index += 1; // consume ;
421+
422+
// Parse update expression
423+
while *index < t.len() && matches!(t[*index].token, Token::LineTerminator) {
424+
*index += 1;
425+
}
426+
let update = if !matches!(t[*index].token, Token::RParen) {
427+
Some(parse_expression(t, index)?)
428+
} else {
429+
None
430+
};
431+
while *index < t.len() && matches!(t[*index].token, Token::LineTerminator) {
432+
*index += 1;
433+
}
434+
if !matches!(t[*index].token, Token::RParen) {
435+
return Err(raise_parse_error_at!(t.get(*index)));
436+
}
437+
*index += 1; // consume )
438+
439+
while *index < t.len() && matches!(t[*index].token, Token::LineTerminator) {
440+
*index += 1;
441+
}
442+
let body = parse_statement_item(t, index)?;
443+
let body_stmts = match *body.kind {
444+
StatementKind::Block(b) => b,
445+
_ => vec![body],
446+
};
447+
448+
let init_stmt = Some(Box::new(Statement {
449+
kind: Box::new(if is_for_await {
450+
StatementKind::AwaitUsing(using_decls)
451+
} else {
452+
StatementKind::Using(using_decls)
453+
}),
454+
line,
455+
column,
456+
}));
457+
let update_stmt = update.map(|e| {
458+
Box::new(Statement {
459+
kind: Box::new(StatementKind::Expr(e)),
460+
line,
461+
column,
462+
})
463+
});
464+
465+
return Ok(Statement {
466+
kind: Box::new(StatementKind::For(Box::new(ForStatement {
467+
init: init_stmt,
468+
test,
469+
update: update_stmt,
470+
body: body_stmts,
471+
}))),
472+
line,
473+
column,
474+
});
475+
}
476+
}
477+
}
478+
273479
// Check for declaration
274480
let is_decl = matches!(t[*index].token, Token::Var | Token::Let | Token::Const);
275481
log::trace!("parse_for_statement: is_decl={} token={:?}", is_decl, t.get(*index));

src/core/statement.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ pub enum StatementKind {
5555
AwaitUsing(Vec<(String, Expr)>), // await using declarations: await using x = expr;
5656
}
5757

58-
#[derive(Clone, Copy, Debug)]
58+
#[derive(Clone, Copy, Debug, PartialEq)]
5959
pub enum VarDeclKind {
6060
Var,
6161
Let,
6262
Const,
63+
Using,
64+
AwaitUsing,
6365
}
6466

6567
unsafe impl<'gc> Collect<'gc> for VarDeclKind {

src/js_array.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,41 @@ pub fn initialize_array<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'g
397397
iterator_proto.borrow_mut(mc).set_non_enumerable(PropertyKey::Symbol(*iter_sym));
398398
}
399399

400+
// Add [Symbol.dispose] to %IteratorPrototype%
401+
// Per spec: %IteratorPrototype% [ @@dispose ] ( ) — calls this.return() if present
402+
if let Some(sym_val) = object_get_key_value(env, "Symbol")
403+
&& let Value::Object(sym_ctor) = &*sym_val.borrow()
404+
&& let Some(dispose_sym_val) = object_get_key_value(sym_ctor, "dispose")
405+
&& let Value::Symbol(dispose_sym) = &*dispose_sym_val.borrow()
406+
{
407+
let dispose_fn_obj = new_js_object_data(mc);
408+
if let Some(func_ctor_val) = crate::core::env_get(env, "Function")
409+
&& let Value::Object(func_ctor) = &*func_ctor_val.borrow()
410+
&& let Some(proto_val) = object_get_key_value(func_ctor, "prototype")
411+
&& let Value::Object(func_proto) = &*proto_val.borrow()
412+
{
413+
dispose_fn_obj.borrow_mut(mc).prototype = Some(*func_proto);
414+
}
415+
dispose_fn_obj.borrow_mut(mc).set_closure(Some(crate::core::new_gc_cell_ptr(
416+
mc,
417+
Value::Function("IteratorPrototype.dispose".to_string()),
418+
)));
419+
slot_set(mc, &dispose_fn_obj, InternalSlot::Callable, &Value::Boolean(true));
420+
let name_desc = crate::core::create_descriptor_object(
421+
mc,
422+
&Value::String(crate::unicode::utf8_to_utf16("[Symbol.dispose]")),
423+
false,
424+
false,
425+
true,
426+
)?;
427+
crate::js_object::define_property_internal(mc, &dispose_fn_obj, "name", &name_desc)?;
428+
let len_desc = crate::core::create_descriptor_object(mc, &Value::Number(0.0), false, false, true)?;
429+
crate::js_object::define_property_internal(mc, &dispose_fn_obj, "length", &len_desc)?;
430+
431+
let desc = crate::core::create_descriptor_object(mc, &Value::Object(dispose_fn_obj), true, false, true)?;
432+
crate::js_object::define_property_internal(mc, &iterator_proto, PropertyKey::Symbol(*dispose_sym), &desc)?;
433+
}
434+
400435
// Store %IteratorPrototype% in env so Iterator helpers init can find it
401436
slot_set(mc, env, InternalSlot::IteratorPrototype, &Value::Object(iterator_proto));
402437

src/js_async_generator.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,37 @@ pub fn initialize_async_generator<'gc>(mc: &MutationContext<'gc>, env: &JSObject
310310
crate::js_object::define_property_internal(mc, &async_iter_proto, *async_iter_sym, &desc_method)?;
311311
}
312312

313+
// Register Symbol.asyncDispose on %AsyncIteratorPrototype%
314+
// Per spec: %AsyncIteratorPrototype% [ @@asyncDispose ] ( ) — returns a Promise
315+
if let Some(sym_ctor) = object_get_key_value(env, "Symbol")
316+
&& let Value::Object(sym_obj) = &*sym_ctor.borrow()
317+
&& let Some(async_dispose_sym_val) = object_get_key_value(sym_obj, "asyncDispose")
318+
&& let Value::Symbol(async_dispose_sym) = &*async_dispose_sym_val.borrow()
319+
{
320+
let fn_obj = new_js_object_data(mc);
321+
fn_obj.borrow_mut(mc).set_closure(Some(new_gc_cell_ptr(
322+
mc,
323+
Value::Function("AsyncIteratorPrototype.asyncDispose".to_string()),
324+
)));
325+
slot_set(mc, &fn_obj, InternalSlot::Callable, &Value::Boolean(true));
326+
if let Some(fp) = func_proto_opt {
327+
fn_obj.borrow_mut(mc).prototype = Some(fp);
328+
}
329+
let desc_name = crate::core::create_descriptor_object(
330+
mc,
331+
&Value::String(crate::unicode::utf8_to_utf16("[Symbol.asyncDispose]")),
332+
false,
333+
false,
334+
true,
335+
)?;
336+
crate::js_object::define_property_internal(mc, &fn_obj, "name", &desc_name)?;
337+
let desc_len = crate::core::create_descriptor_object(mc, &Value::Number(0.0), false, false, true)?;
338+
crate::js_object::define_property_internal(mc, &fn_obj, "length", &desc_len)?;
339+
340+
let desc_method = crate::core::create_descriptor_object(mc, &Value::Object(fn_obj), true, false, true)?;
341+
crate::js_object::define_property_internal(mc, &async_iter_proto, *async_dispose_sym, &desc_method)?;
342+
}
343+
313344
// Set AsyncGenerator.prototype[@@toStringTag] = "AsyncGenerator"
314345
if let Some(sym_ctor) = object_get_key_value(env, "Symbol")
315346
&& let Value::Object(sym_obj) = &*sym_ctor.borrow()

0 commit comments

Comments
 (0)