LibJS: Avoid naming parenthesized assignment functions

NamedEvaluation for assignment only applies when the left hand side is
an IdentifierRef. Parenthesized identifiers are still assignment
targets, but are not IdentifierRefs, so `(fn) = function () {}` must
leave the function anonymous.

Carry the parser's parenthesized-LHS bit into assignment codegen. Use
it when setting pending_lhs_name for plain and logical assignment, and
cover parenthesized assignment targets in the function-name tests.
This commit is contained in:
Andreas Kling 2026-05-21 16:31:36 +02:00 committed by Andreas Kling
parent 22e1df58d3
commit 62ac304099
4 changed files with 52 additions and 5 deletions

View file

@ -1537,6 +1537,7 @@ pub struct AssignmentExprData {
pub op: AssignmentOp,
pub lhs: AssignmentLhs,
pub rhs: Box<Expression>,
pub lhs_is_parenthesized: bool,
}
#[derive(Clone, Debug)]

View file

@ -187,6 +187,22 @@ fn emit_typeof_binding(generator: &mut Generator, dst: Operand, identifier: Iden
}
}
fn set_pending_lhs_name_for_identifier_assignment(
generator: &mut Generator,
identifier: StringId,
lhs_is_parenthesized: bool,
) {
// https://tc39.es/ecma262/#sec-assignment-operators-runtime-semantics-evaluation
// If IsAnonymousFunctionDefinition(|AssignmentExpression|) is *true* and IsIdentifierRef of
// |LeftHandSideExpression| is *true*, then
// A parenthesized identifier can be a simple assignment target, but it is not an IdentifierRef.
generator.pending_lhs_name = if lhs_is_parenthesized {
None
} else {
Some(generator.intern_identifier_id(identifier))
};
}
fn generate_expression_inner(
expression: &Expression,
generator: &mut Generator,
@ -354,9 +370,14 @@ fn generate_expression_inner(
ExpressionKind::Update(data) => generate_update_expression(generator, data.op, &data.argument, data.prefixed),
// === Assignment ===
ExpressionKind::Assignment(data) => {
generate_assignment_expression(generator, data.op, &data.lhs, &data.rhs, preferred_dst)
}
ExpressionKind::Assignment(data) => generate_assignment_expression(
generator,
data.op,
&data.lhs,
&data.rhs,
data.lhs_is_parenthesized,
preferred_dst,
),
// === Template literals ===
ExpressionKind::TemplateLiteral(data) => generate_template_literal(generator, data, preferred_dst),
@ -3555,6 +3576,7 @@ fn generate_assignment_expression(
op: AssignmentOp,
lhs: &AssignmentLhs,
rhs: &Expression,
lhs_is_parenthesized: bool,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
match lhs {
@ -3565,7 +3587,7 @@ fn generate_assignment_expression(
let arena = generator.arena.clone();
let ident = &arena.identifiers[id];
if op == AssignmentOp::Assignment {
generator.pending_lhs_name = Some(generator.intern_identifier_id(ident.name));
set_pending_lhs_name_for_identifier_assignment(generator, ident.name, lhs_is_parenthesized);
let rhs_val = generate_expression(rhs, generator, None)?;
generator.pending_lhs_name = None;
if ident.is_local() {
@ -3606,7 +3628,11 @@ fn generate_assignment_expression(
}
// RHS block: evaluate RHS, assign, jump to end.
generator.switch_to_basic_block(rhs_block);
generator.pending_lhs_name = Some(generator.intern_identifier_id(arena.identifiers[id].name));
set_pending_lhs_name_for_identifier_assignment(
generator,
arena.identifiers[id].name,
lhs_is_parenthesized,
);
let rhs_val = generate_expression(rhs, generator, None)?;
generator.pending_lhs_name = None;
// Allocate dst after RHS evaluation.

View file

@ -863,6 +863,7 @@ impl Parser<'_> {
op,
lhs: AssignmentLhs::Pattern(binding_pattern),
rhs: Box::new(rhs),
lhs_is_parenthesized,
})),
),
ForbiddenTokens::none(),
@ -890,6 +891,7 @@ impl Parser<'_> {
op,
lhs: AssignmentLhs::Expression(Box::new(lhs)),
rhs: Box::new(rhs),
lhs_is_parenthesized,
})),
),
ForbiddenTokens::none(),

View file

@ -124,4 +124,22 @@ describe("some anonymous functions get renamed", () => {
let f4 = false || function () {};
expect(f4.name).toBe("");
});
test("parenthesized assignment target does not name", () => {
let f5;
eval("(f5) = function () {};");
expect(f5.name).toBe("");
let f6;
eval("(f6) ??= function () {};");
expect(f6.name).toBe("");
let f7 = false;
eval("(f7) ||= function () {};");
expect(f7.name).toBe("");
let f8 = true;
eval("(f8) &&= function () {};");
expect(f8.name).toBe("");
});
});