Now that LibRegex is safe to use (for parsing) off the main thread,
we can validate regex literals directly while parsing JavaScript.
This allows us to remove the deferred regex compilation pass that we
previously ran on the main thread after parsing JS in the background.
- Restrict catch parameter conflict check to only direct children
of the catch body block, not nested scopes
- Set new_target_is_valid for dynamic function compilation (new
Function)
- Move check_parameters_post_body before flag restoration in
parse_method_definition so generator methods inside static init
blocks correctly allow 'await' as a parameter name
- Reject duplicate bindings in catch parameter patterns
- Reject redeclaration of catch parameter with let/const/function
- Reject binding patterns with initializers in for-in heads (AnnexB
only permits simple BindingIdentifier with initializer)
- Reject 'await' as binding identifier in class static init blocks
and module code
Check identifier name validity for destructuring assignment pattern
bound names, and validate arrow function parameters after the arrow
is confirmed rather than during speculative parameter parsing.
This fixes arguments/eval as destructuring assignment targets and as
arrow function parameter names in strict mode.
Arrow functions don't have their own new.target binding -- they
inherit from the enclosing scope. At the global level, there is no
enclosing function, so new.target inside a global arrow is invalid.
Add a new_target_is_valid flag to ParserFlags that is set to true
when entering regular (non-arrow) function bodies, method
definitions, and class static init blocks. Arrow functions inherit
the flag from their enclosing scope rather than setting it.
- Reject `true`, `false`, `null` as label identifiers
- Reject generator declarations in if-statement bodies (not covered
by Annex B)
- Reject `await` as label in class static init blocks and modules
- Reject `arguments` in class static initialization blocks
- Reject generator shorthand without method body in object literals
- Reject `get constructor()` / `set constructor()` in class bodies
- Reject `super.#private` member access
Fix the static semantics for AssignmentTargetType in both the C++ and
Rust parsers:
- NewExpression is never a valid assignment target. Previously, the C++
parser's is<CallExpression> check matched NewExpression since it
inherits from CallExpression in our AST. Add explicit
!is<NewExpression> guards everywhere.
- CallExpression as assignment target is only valid in non-strict mode
(web-compat "runtime error for function call assignment targets").
Pass strict_mode to is_simple_assignment_target and reject call
expressions in strict mode for assignment, compound assignment,
prefix/postfix update, and for-in/of LHS positions.
- Parenthesized ObjectLiteral/ArrayLiteral (e.g. `({}) = 1`) must not
be treated as destructuring patterns. Track whether the primary
expression was parenthesized and skip binding pattern synthesis.
Update existing tests that were testing incorrect behavior:
- `'use strict'; foo() = 'foo'` is now correctly a SyntaxError
- for-in/of with call expression LHS: use toEval() instead of
toEvalTo() (which runs eval inside a class method, i.e. strict mode)
Move regex compilation out of the parsing hot path. Both the C++ and
Rust parsers now collect raw regex pattern+flags strings during parsing
and batch-compile them after parsing completes.
This is a prerequisite for moving the Rust parser to a background
thread, since LibRegex is thread-unsafe and FFI calls during parsing
prevent parallelization.
Flag validation remains in the parser since it's trivial string
checking with no LibRegex dependency.
CompiledRegex held an FFI handle with unique ownership and panicked
on clone. This caused a crash when a class field initializer contained
a regex literal, since the codegen wraps field initializers in a
synthetic function body by cloning the expression.
Wrapping CompiledRegex in Rc makes the clone a cheap refcount bump.
The take() semantics are preserved: the first codegen path to call
take() gets the handle, and Drop frees it if nobody took it.
Cache failed arrow function attempts by token offset. Once we
determine that '(' at offset N is not the start of an arrow
function, skip re-attempting at the same offset.
Without memoization, nested expressions like (a=(b=(c=(d=0))))
cause exponential work: each failed arrow attempt at an outer '('
re-parses all inner '(' positions during grouping expression
re-parse, and each inner position triggers its own arrow
attempts. With n nesting levels, the innermost position is
processed O(2^n) times.
The C++ parser already has this optimization (via the
try_parse_arrow_function_expression_failed_at_position()
memoization cache).
Implement a complete Rust reimplementation of the LibJS frontend:
lexer, parser, AST, scope collector, and bytecode code generator.
The Rust pipeline is built via Corrosion (CMake-Cargo bridge) and
linked into LibJS as a static library. It is gated behind a build
flag (ENABLE_RUST, on by default except on Windows) and two runtime
environment variables:
- LIBJS_CPP: Use the C++ pipeline instead of Rust
- LIBJS_COMPARE_PIPELINES=1: Run both pipelines in lockstep,
aborting on any difference in AST or bytecode generated.
The C++ side communicates with Rust through a C FFI layer
(RustIntegration.cpp/h) that passes source text to Rust and receives
a populated Executable back via a BytecodeFactory interface.