diff --git a/Jint.Tests/Runtime/PlainAssignmentTests.cs b/Jint.Tests/Runtime/PlainAssignmentTests.cs
new file mode 100644
index 000000000..b10ac4eb1
--- /dev/null
+++ b/Jint.Tests/Runtime/PlainAssignmentTests.cs
@@ -0,0 +1,96 @@
+using Jint.Runtime;
+
+namespace Jint.Tests.Runtime;
+
+///
+/// Plain assignment to slot-stored bindings takes a fast lane; these pin its parity with the
+/// materialized AssignToIdentifier path: anonymous function/class naming, abrupt completions,
+/// const/TDZ error ordering, and targets rewritten during right-hand side evaluation.
+///
+public class PlainAssignmentTests
+{
+ [Fact]
+ public void AnonymousFunctionAndClassGetAssignedName()
+ {
+ var engine = new Engine(static options => options.Strict());
+ var result = engine.Evaluate("""
+ function f() {
+ var g = function () {};
+ var C = class {};
+ var named = function realName() {};
+ var arrow = () => {};
+ return [g.name, C.name, named.name, arrow.name].join('|');
+ }
+ f();
+ """).AsString();
+
+ Assert.Equal("g|C|realName|arrow", result);
+ }
+
+ [Fact]
+ public void ValuePositionAndChainedAssignments()
+ {
+ var engine = new Engine(static options => options.Strict());
+ var result = engine.Evaluate("""
+ function f() {
+ var a = 1, b, c;
+ c = (b = a);
+ return '' + b + c;
+ }
+ f();
+ """).AsString();
+
+ Assert.Equal("11", result);
+ }
+
+ [Fact]
+ public void ThrowingRightHandSideDoesNotAssign()
+ {
+ var engine = new Engine(static options => options.Strict());
+ var result = engine.Evaluate("""
+ function f() {
+ var a = 'initial';
+ try { a = (function () { throw new Error('x'); })(); } catch (e) { }
+ return a;
+ }
+ f();
+ """).AsString();
+
+ Assert.Equal("initial", result);
+ }
+
+ [Fact]
+ public void RightHandSideRewritingTargetIsOverwritten()
+ {
+ var engine = new Engine(static options => options.Strict());
+ var result = engine.Evaluate("function f() { var b = 0; b = (b = 5, 9); return b; } f();").AsNumber();
+
+ Assert.Equal(9, result);
+ }
+
+ [Fact]
+ public void ConstTargetThrowsTypeErrorAfterEvaluatingRightHandSide()
+ {
+ var engine = new Engine(static options => options.Strict());
+
+ var ex = Assert.Throws(() => engine.Execute("""
+ function f() {
+ const c = 1;
+ var order = [];
+ try { c = (order.push('rhs'), 2); } finally { globalThis.order = order.join(','); }
+ }
+ f();
+ """));
+ Assert.True(ex.Error.InstanceofOperator(engine.Intrinsics.TypeError));
+ Assert.Equal("rhs", engine.Evaluate("globalThis.order").AsString());
+ }
+
+ [Fact]
+ public void AssignmentBeforeLetDeclarationThrowsReferenceError()
+ {
+ var engine = new Engine(static options => options.Strict());
+
+ var ex = Assert.Throws(() => engine.Execute("function f() { { x = 1; let x; } } f();"));
+ Assert.True(ex.Error.InstanceofOperator(engine.Intrinsics.ReferenceError));
+ }
+}
diff --git a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs
index 82999ef34..dbbd3911e 100644
--- a/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs
+++ b/Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs
@@ -716,11 +716,99 @@ protected override object EvaluateInternal(EvaluationContext context)
object? completion = null;
if (_leftIdentifier != null)
{
+ // a populated global-binding cache means the target is a global — the slot lane
+ // can never apply, and AssignToIdentifier's cached-global arm is the fast path
+ if (_leftIdentifier._cachedGlobalEnv is null
+ && !_evalOrArguments
+ && !_leftIsCoverParenthesized
+ && TryAssignSlot(context, out var slotResult))
+ {
+ return slotResult;
+ }
+
completion = AssignToIdentifier(context, _leftIdentifier, _right, _evalOrArguments, !_leftIsCoverParenthesized);
}
return completion ?? SetValue(context);
}
+ ///
+ /// Plain assignment to a slot-stored binding (`b = a`, `x = f()` on locals): resolves the
+ /// target through the slot-location cache instead of the per-assignment environment walk,
+ /// mirroring 's right-hand side semantics exactly
+ /// (anonymous function/class naming, abrupt and generator-abort completions). Const and
+ /// TDZ targets bail before any evaluation so the slow path produces the spec error
+ /// ordering; the target binding is re-validated after the right-hand side runs since it
+ /// may have been written (or in degenerate cases deleted) during evaluation.
+ ///
+ private bool TryAssignSlot(EvaluationContext context, out JsValue result)
+ {
+ result = null!;
+
+ var engine = context.Engine;
+ if (engine.ExecutionContext.Suspendable is not null)
+ {
+ return false;
+ }
+
+ if (!_lhsSlotCache.TryResolve(engine, engine.ExecutionContext.LexicalEnvironment, _leftIdentifier!.Identifier, out var environment, out var slotIndex))
+ {
+ return false;
+ }
+
+ var slots = environment._slots;
+ if (slots is null || (uint) slotIndex >= (uint) slots.Length)
+ {
+ return false;
+ }
+
+ {
+ ref var binding = ref slots[slotIndex];
+ if (!binding.Mutable || !binding.IsInitialized())
+ {
+ return false;
+ }
+ }
+
+ JsValue completion;
+ var right = _right;
+ if (right is JintClassExpression classExpression && right._expression.IsAnonymousFunctionDefinition())
+ {
+ completion = classExpression.EvaluateWithName(context, _leftIdentifier.Identifier.Value.ToString());
+ }
+ else
+ {
+ completion = right.GetValue(context);
+ }
+
+ if (context.IsAbrupt() || context.IsGeneratorAborted())
+ {
+ result = completion;
+ return true;
+ }
+
+ var rval = completion.Clone();
+
+ if (right._expression.IsFunctionDefinition() && right is not JintClassExpression)
+ {
+ ((Function) rval).SetFunctionName(_leftIdentifier.Identifier.Value);
+ }
+
+ ref var bindingAfterRight = ref slots[slotIndex];
+ if (bindingAfterRight.Mutable && bindingAfterRight.IsInitialized())
+ {
+ slots[slotIndex] = bindingAfterRight.ChangeValue(rval);
+ }
+ else
+ {
+ // degenerate: the right-hand side changed the binding's state; the full store
+ // produces the exact semantics
+ environment.SetMutableBinding(_leftIdentifier.Identifier, rval, StrictModeScope.IsStrictModeCode);
+ }
+
+ result = rval;
+ return true;
+ }
+
internal override bool HasDiscardFastPath => _structurallyNumeric;
internal override void EvaluateAndDiscard(EvaluationContext context)