diff --git a/docs/plans/archive/issue-1956-resolution.md b/docs/plans/archive/issue-1956-resolution.md
new file mode 100644
index 000000000..872303c55
--- /dev/null
+++ b/docs/plans/archive/issue-1956-resolution.md
@@ -0,0 +1,34 @@
+# Issue #1956: escaped numeric closure verification
+
+Verified on Windows ARM64, .NET SDK 10.0.401/runtime 10.0.12, Release, on
+2026-10-04. The existing `EscapedNumericClosureKeepsLiveUpdates` source and its
+`13 14\ntrue Infinity\n` expectation remain enabled and unchanged.
+
+An independently restored/built archive of reported baseline
+`78931b4add5a6937ab6e63452f2e0ad97fe12adb` reproduces the test-host access violation
+in `CastHelpers.IsInstanceOfClass`, called from `$Program.create(Double)`.
+Compiling that same source with the baseline CLI and `--standalone --verify`
+reports:
+
+```text
+[IL Error] $Program.create: StackObjRef {Offset=98, Found=Double} - Expected an ObjRef on the stack.
+```
+
+The emitted method loads the captured `Double current` field at IL offset
+`0x005C`, then applies `isinst $LexicalUninitialized` at `0x0062` without boxing.
+The verifier therefore identifies an invalid object-reference operation,
+independently of the execution exception.
+
+An independently restored/built archive of current main
+`987a8b58d5fe32ddac4d8edd25384c923665e301` passes both original API modes, and its
+standalone assembly passes IL verification. Its generated method omits that
+sentinel check on the promoted numeric slot. The responsible existing repair is
+`1331f8f06` (PR #1969), in `ILEmitter.EmitLexicalAssignmentTdzGuard`: value-type
+capture fields cannot hold the object-valued TDZ sentinel, so the guard returns
+before loading and inspecting them.
+
+This consolidation adds deployment coverage for the original source and live
+update/TDZ controls, including both declaration paths, verified standalone
+execution and actual hosted initialization. It credits the existing product
+repair; it does not claim a new compiler repair or infer behavior on other
+architectures from the local exception.
diff --git a/src/SharpTS/Compilation/Emitters/GlobalThisStaticEmitter.cs b/src/SharpTS/Compilation/Emitters/GlobalThisStaticEmitter.cs
index 60d1b8997..a685ec960 100644
--- a/src/SharpTS/Compilation/Emitters/GlobalThisStaticEmitter.cs
+++ b/src/SharpTS/Compilation/Emitters/GlobalThisStaticEmitter.cs
@@ -85,6 +85,13 @@ public bool TryEmitStaticPropertyGet(IEmitterContext emitter, string propertyNam
if (TryEmitBuiltInClassType(il, ctx, propertyName))
return true;
+ if (propertyName == "Math")
+ {
+ il.Emit(OpCodes.Ldstr, propertyName);
+ il.Emit(OpCodes.Call, ctx.Runtime!.GlobalObject.GetProperty);
+ return true;
+ }
+
// Check if this is a known built-in that has its own static emitter
var staticEmitter = _registry.GetStaticStrategy(propertyName);
if (staticEmitter != null)
diff --git a/src/SharpTS/Compilation/ExpressionEmitterBase.Operators.cs b/src/SharpTS/Compilation/ExpressionEmitterBase.Operators.cs
index 4fe00d08c..8f15da458 100644
--- a/src/SharpTS/Compilation/ExpressionEmitterBase.Operators.cs
+++ b/src/SharpTS/Compilation/ExpressionEmitterBase.Operators.cs
@@ -10,6 +10,19 @@ namespace SharpTS.Compilation;
///
public abstract partial class ExpressionEmitterBase
{
+ // PutValue uses the strictness of the expression's enclosing code, including
+ // state-machine bodies. Call only after the read and any short-circuit check.
+ protected void EmitPropertyWrite(bool indexed)
+ {
+ if (Ctx.IsStrictMode)
+ {
+ IL.Emit(OpCodes.Ldc_I4_1);
+ IL.Emit(OpCodes.Call, indexed ? Ctx.Runtime!.ObjectWrite.IndexStrict : Ctx.Runtime!.ObjectWrite.PropertyStrict);
+ }
+ else
+ IL.Emit(OpCodes.Call, indexed ? Ctx.Runtime!.ObjectWrite.Index : Ctx.Runtime!.ObjectWrite.Property);
+ }
+
///
/// Emits the logical condition check for &&=, ||=, ??= operators.
/// After this, the stack has the current value and control flows to either
@@ -253,7 +266,7 @@ protected virtual void EmitPrefixIncrement(Expr.PrefixIncrement pi)
EmitMemberAccessIncrement(
isPrefix: true, pi.Operator.Type, objLocal,
emitKey: () => IL.Emit(OpCodes.Ldstr, get.Name.Lexeme),
- Ctx.Runtime!.ObjectRead.Property, Ctx.Runtime!.ObjectWrite.Property);
+ Ctx.Runtime!.ObjectRead.Property, indexed: false);
return;
}
@@ -265,7 +278,7 @@ protected virtual void EmitPrefixIncrement(Expr.PrefixIncrement pi)
EmitMemberAccessIncrement(
isPrefix: true, pi.Operator.Type, objLocal,
emitKey: () => IL.Emit(OpCodes.Ldloc, indexLocal),
- Ctx.Runtime!.ObjectRead.Index, Ctx.Runtime!.ObjectWrite.Index);
+ Ctx.Runtime!.ObjectRead.Index, indexed: true);
return;
}
@@ -314,7 +327,7 @@ protected virtual void EmitPostfixIncrement(Expr.PostfixIncrement poi)
EmitMemberAccessIncrement(
isPrefix: false, poi.Operator.Type, objLocal,
emitKey: () => IL.Emit(OpCodes.Ldstr, get.Name.Lexeme),
- Ctx.Runtime!.ObjectRead.Property, Ctx.Runtime!.ObjectWrite.Property);
+ Ctx.Runtime!.ObjectRead.Property, indexed: false);
return;
}
@@ -326,7 +339,7 @@ protected virtual void EmitPostfixIncrement(Expr.PostfixIncrement poi)
EmitMemberAccessIncrement(
isPrefix: false, poi.Operator.Type, objLocal,
emitKey: () => IL.Emit(OpCodes.Ldloc, indexLocal),
- Ctx.Runtime!.ObjectRead.Index, Ctx.Runtime!.ObjectWrite.Index);
+ Ctx.Runtime!.ObjectRead.Index, indexed: true);
return;
}
@@ -400,7 +413,7 @@ private bool TryEmitNativeNumericLocalIncrement(
///
private void EmitMemberAccessIncrement(
bool isPrefix, TokenType op, LocalBuilder objLocal, Action emitKey,
- MethodBuilder getMethod, MethodBuilder setMethod)
+ MethodBuilder getMethod, bool indexed)
{
double delta = op == TokenType.PLUS_PLUS ? 1.0 : -1.0;
@@ -418,7 +431,7 @@ private void EmitMemberAccessIncrement(
IL.Emit(OpCodes.Ldloc, objLocal);
emitKey();
IL.Emit(OpCodes.Ldloc, newValue);
- IL.Emit(OpCodes.Call, setMethod);
+ EmitPropertyWrite(indexed);
IL.Emit(OpCodes.Ldloc, resultValue);
SetStackUnknown();
@@ -503,7 +516,7 @@ private bool TryEmitStaticFieldIncrement(Expr.Get get, bool isPrefix, TokenType
IL.Emit(OpCodes.Call, Types.TypeGetTypeFromHandle);
IL.Emit(OpCodes.Ldstr, get.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, newValue);
- IL.Emit(OpCodes.Call, Ctx.Runtime!.ObjectWrite.Property);
+ EmitPropertyWrite(indexed: false);
IL.Emit(OpCodes.Ldloc, resultValue);
SetStackUnknown();
@@ -543,7 +556,7 @@ protected virtual void EmitLogicalSet(Expr.LogicalSet ls)
IL.Emit(OpCodes.Ldloc, objLocal);
IL.Emit(OpCodes.Ldstr, ls.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, resultLocal);
- IL.Emit(OpCodes.Call, Ctx.Runtime!.ObjectWrite.Property);
+ EmitPropertyWrite(indexed: false);
IL.Emit(OpCodes.Ldloc, resultLocal);
IL.Emit(OpCodes.Br, endLabel);
@@ -583,7 +596,7 @@ protected virtual void EmitLogicalSetIndex(Expr.LogicalSetIndex lsi)
IL.Emit(OpCodes.Ldloc, objLocal);
IL.Emit(OpCodes.Ldloc, indexLocal);
IL.Emit(OpCodes.Ldloc, resultLocal);
- IL.Emit(OpCodes.Call, Ctx.Runtime!.ObjectWrite.Index);
+ EmitPropertyWrite(indexed: true);
IL.Emit(OpCodes.Ldloc, resultLocal);
IL.Emit(OpCodes.Br, endLabel);
@@ -641,7 +654,7 @@ protected virtual void EmitCompoundSet(Expr.CompoundSet cs)
IL.Emit(OpCodes.Call, Types.TypeGetTypeFromHandle);
IL.Emit(OpCodes.Ldstr, cs.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, inheritedResultTemp);
- IL.Emit(OpCodes.Call, Ctx.Runtime!.ObjectWrite.Property);
+ EmitPropertyWrite(indexed: false);
IL.Emit(OpCodes.Ldloc, inheritedResultTemp);
SetStackUnknown();
@@ -677,7 +690,7 @@ protected virtual void EmitCompoundSet(Expr.CompoundSet cs)
IL.Emit(OpCodes.Ldloc, objTemp);
IL.Emit(OpCodes.Ldstr, cs.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, resultLocal);
- IL.Emit(OpCodes.Call, Ctx.Runtime!.ObjectWrite.Property);
+ EmitPropertyWrite(indexed: false);
IL.Emit(OpCodes.Ldloc, resultLocal);
SetStackUnknown();
@@ -717,7 +730,7 @@ protected virtual void EmitCompoundSetIndex(Expr.CompoundSetIndex csi)
IL.Emit(OpCodes.Ldloc, objTemp);
IL.Emit(OpCodes.Ldloc, indexTemp);
IL.Emit(OpCodes.Ldloc, resultLocal);
- IL.Emit(OpCodes.Call, Ctx.Runtime!.ObjectWrite.Index);
+ EmitPropertyWrite(indexed: true);
IL.Emit(OpCodes.Ldloc, resultLocal);
SetStackUnknown();
diff --git a/src/SharpTS/Compilation/ExpressionEmitterBase.cs b/src/SharpTS/Compilation/ExpressionEmitterBase.cs
index 4954d0490..40001008e 100644
--- a/src/SharpTS/Compilation/ExpressionEmitterBase.cs
+++ b/src/SharpTS/Compilation/ExpressionEmitterBase.cs
@@ -369,8 +369,7 @@ public virtual void EmitExpression(Expr expr)
///
/// Emits a literal value. Default uses helper methods (EmitNullConstant, EmitDoubleConstant, etc.).
- /// ILEmitter overrides for BigInteger/SharpTSUndefined support.
- /// AsyncArrowMoveNextEmitter overrides for eager boxing.
+ /// BigInteger literals are boxed consistently in synchronous and state-machine bodies.
///
protected virtual void EmitLiteral(Expr.Literal lit)
{
@@ -380,6 +379,7 @@ protected virtual void EmitLiteral(Expr.Literal lit)
case double d: EmitDoubleConstant(d); break;
case bool b: EmitBoolConstant(b); break;
case string s: EmitStringConstant(s); break;
+ case System.Numerics.BigInteger integer: EmitBigIntConstant(integer); break;
// The `undefined` keyword parses to a Literal holding the $Undefined sentinel
// (Parser.Expressions.cs), as do array holes. State-machine emitters use this base
// method, so without an explicit arm `undefined` would fall to `default` and emit
@@ -391,6 +391,22 @@ protected virtual void EmitLiteral(Expr.Literal lit)
}
}
+ protected void EmitBigIntConstant(System.Numerics.BigInteger value)
+ {
+ if (value >= long.MinValue && value <= long.MaxValue)
+ {
+ IL.Emit(OpCodes.Ldc_I8, (long)value);
+ IL.Emit(OpCodes.Newobj, Types.GetConstructor(Types.BigInteger, Types.Int64));
+ }
+ else
+ {
+ IL.Emit(OpCodes.Ldstr, value.ToString(System.Globalization.CultureInfo.InvariantCulture));
+ IL.Emit(OpCodes.Call, Types.GetMethod(Types.BigInteger, "Parse", Types.String));
+ }
+ IL.Emit(OpCodes.Box, Types.BigInteger);
+ SetStackUnknown();
+ }
+
///
/// Emits an expression boxed and spills it into a fresh object local.
/// State-machine emitters (the only users of these base implementations) can suspend
@@ -2284,6 +2300,7 @@ protected bool TryEmitBuiltInClassType(string name)
// bare Symbol in its own pseudo-variable arm; this entry covers the
// state-machine emitters that resolve through this base path.
"Symbol" => Ctx.Runtime!.Symbols.Type,
+ "BigInt" => Types.BigInteger,
// Omitted Web stream constructors fall through to ThrowUndefinedVariable.
"ReadableStream" => Ctx.Runtime!.WebStreams?.ReadableType,
"WritableStream" => Ctx.Runtime!.WebStreams?.WritableType,
@@ -2623,6 +2640,12 @@ protected virtual void EmitLogical(Expr.Logical l)
///
protected virtual void EmitUnary(Expr.Unary u)
{
+ if (u.Operator.Type == TokenType.MINUS && u.Right is Expr.Literal { Value: System.Numerics.BigInteger value })
+ {
+ EmitBigIntConstant(-value);
+ return;
+ }
+
switch (u.Operator.Type)
{
case TokenType.MINUS:
diff --git a/src/SharpTS/Compilation/ILEmitter.Expressions.cs b/src/SharpTS/Compilation/ILEmitter.Expressions.cs
index 8567916b0..7c60ef138 100644
--- a/src/SharpTS/Compilation/ILEmitter.Expressions.cs
+++ b/src/SharpTS/Compilation/ILEmitter.Expressions.cs
@@ -24,20 +24,7 @@ protected override void EmitLiteral(Expr.Literal lit)
EmitBoolConstant(b);
break;
case System.Numerics.BigInteger bi:
- if (bi >= long.MinValue && bi <= long.MaxValue)
- {
- // Optimization: Use BigInteger(long) constructor for small values
- IL.Emit(OpCodes.Ldc_I8, (long)bi);
- IL.Emit(OpCodes.Newobj, _ctx.Types.GetConstructor(_ctx.Types.BigInteger, _ctx.Types.Int64));
- }
- else
- {
- // Fallback: Parse from string for large values
- IL.Emit(OpCodes.Ldstr, bi.ToString());
- IL.Emit(OpCodes.Call, _ctx.Types.GetMethod(_ctx.Types.BigInteger, "Parse", _ctx.Types.String));
- }
- IL.Emit(OpCodes.Box, _ctx.Types.BigInteger);
- SetStackUnknown();
+ EmitBigIntConstant(bi);
break;
case Runtime.Types.SharpTSUndefined:
EmitUndefinedConstant();
@@ -1001,22 +988,32 @@ protected override void EmitTaggedTemplateLiteral(Expr.TaggedTemplateLiteral ttl
}
// Detect property access tag (obj.method`...`) for this binding
- bool hasThisBinding = ttl.Tag is Expr.Get;
+ bool hasThisBinding = ttl.Tag is Expr.Get or Expr.GetIndex;
LocalBuilder? receiverLocal = null;
// 1. Emit the tag function reference (and receiver for property access tags)
if (hasThisBinding)
{
- var g = (Expr.Get)ttl.Tag;
+ var receiver = ttl.Tag is Expr.Get g ? g.Object : ((Expr.GetIndex)ttl.Tag).Object;
// Emit and save the receiver object
- EmitExpression(g.Object);
+ EmitExpression(receiver);
EnsureBoxed();
receiverLocal = _ctx.ILBuilder.DeclareLocal(_ctx.Types.Object);
IL.Emit(OpCodes.Stloc, receiverLocal);
// Get the method: GetProperty(obj, name) — handles all object types including dictionaries
IL.Emit(OpCodes.Ldloc, receiverLocal);
- IL.Emit(OpCodes.Ldstr, g.Name.Lexeme);
- IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectRead.Property);
+ if (ttl.Tag is Expr.Get property)
+ {
+ IL.Emit(OpCodes.Ldstr, property.Name.Lexeme);
+ IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectRead.Property);
+ }
+ else
+ {
+ var index = ((Expr.GetIndex)ttl.Tag).Index;
+ EmitExpression(index);
+ EmitBoxIfNeeded(index);
+ IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectRead.Index);
+ }
// Push thisArg (receiver) for WithThis call
IL.Emit(OpCodes.Ldloc, receiverLocal);
}
diff --git a/src/SharpTS/Compilation/ILEmitter.Operators.cs b/src/SharpTS/Compilation/ILEmitter.Operators.cs
index cb7a0dcc8..a7dc2f033 100644
--- a/src/SharpTS/Compilation/ILEmitter.Operators.cs
+++ b/src/SharpTS/Compilation/ILEmitter.Operators.cs
@@ -1107,7 +1107,7 @@ protected override void EmitLogicalSet(Expr.LogicalSet ls)
var resultLocal = IL.DeclareLocal(_ctx.Types.Object);
IL.Emit(OpCodes.Dup);
IL.Emit(OpCodes.Stloc, resultLocal);
- IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Property);
+ EmitPropertyWrite(indexed: false);
IL.Emit(OpCodes.Ldloc, resultLocal);
builder.Emit_Br(endLabel);
@@ -1181,7 +1181,7 @@ protected override void EmitLogicalSetIndex(Expr.LogicalSetIndex lsi)
var resultLocal = IL.DeclareLocal(_ctx.Types.Object);
IL.Emit(OpCodes.Dup);
IL.Emit(OpCodes.Stloc, resultLocal);
- IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Index);
+ EmitPropertyWrite(indexed: true);
IL.Emit(OpCodes.Ldloc, resultLocal);
builder.Emit_Br(endLabel);
@@ -2252,7 +2252,7 @@ private void EmitIncrementProperty(Expr.Get get, bool isIncrement, bool isPrefix
IL.Emit(OpCodes.Ldloc, receiver);
IL.Emit(OpCodes.Ldstr, get.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, newValue);
- IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Property);
+ EmitPropertyWrite(indexed: false);
IL.Emit(OpCodes.Ldloc, isPrefix ? newValue : oldValue);
SetStackUnknown();
}
@@ -2276,7 +2276,7 @@ private void EmitIncrementIndex(Expr.GetIndex gi, bool isIncrement, bool isPrefi
IL.Emit(OpCodes.Ldloc, receiver);
IL.Emit(OpCodes.Ldloc, index);
IL.Emit(OpCodes.Ldloc, newValue);
- IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Index);
+ EmitPropertyWrite(indexed: true);
IL.Emit(OpCodes.Ldloc, isPrefix ? newValue : oldValue);
SetStackUnknown();
}
@@ -2896,7 +2896,7 @@ private bool TryEmitStaticFieldIncrement(Expr.Get get, bool isIncrement, bool is
IL.Emit(OpCodes.Call, _ctx.Types.TypeGetTypeFromHandle);
IL.Emit(OpCodes.Ldstr, get.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, newValue);
- IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Property);
+ EmitPropertyWrite(indexed: false);
}
if (isPrefix)
@@ -2965,7 +2965,7 @@ protected override void EmitCompoundSet(Expr.CompoundSet cs)
IL.Emit(OpCodes.Call, _ctx.Types.TypeGetTypeFromHandle);
IL.Emit(OpCodes.Ldstr, cs.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, inheritedResultCS);
- IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Property);
+ EmitPropertyWrite(indexed: false);
IL.Emit(OpCodes.Ldloc, inheritedResultCS);
SetStackUnknown();
@@ -2998,7 +2998,7 @@ protected override void EmitCompoundSet(Expr.CompoundSet cs)
IL.Emit(OpCodes.Ldloc, objectLocal);
IL.Emit(OpCodes.Ldstr, cs.Name.Lexeme);
IL.Emit(OpCodes.Ldloc, resultLocal);
- IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Property);
+ EmitPropertyWrite(indexed: false);
// Leave result on stack
IL.Emit(OpCodes.Ldloc, resultLocal);
@@ -3125,7 +3125,7 @@ protected override void EmitCompoundSetIndex(Expr.CompoundSetIndex csi)
IL.Emit(OpCodes.Ldloc, objLocal);
IL.Emit(OpCodes.Ldloc, indexLocal);
IL.Emit(OpCodes.Ldloc, resultLocal);
- IL.Emit(OpCodes.Call, _ctx.Runtime!.ObjectWrite.Index);
+ EmitPropertyWrite(indexed: true);
// Leave result on stack
IL.Emit(OpCodes.Ldloc, resultLocal);
diff --git a/src/SharpTS/Compilation/RuntimeEmitter.Coercion.cs b/src/SharpTS/Compilation/RuntimeEmitter.Coercion.cs
index a8827d40a..66b5885a3 100644
--- a/src/SharpTS/Compilation/RuntimeEmitter.Coercion.cs
+++ b/src/SharpTS/Compilation/RuntimeEmitter.Coercion.cs
@@ -29,17 +29,6 @@ private readonly record struct AbstractNumberInputs(
MethodInfo CreateException,
ConstructorInfo TypeErrorCtor);
- private readonly record struct ExplicitNumberInputs(
- Type UndefinedType,
- Type SymbolType,
- Type ObjectType,
- MethodInfo GetProperty,
- MethodInfo InvokeMethodValue,
- MethodInfo ToJsString,
- MethodInfo BigIntToNumber,
- MethodInfo CreateException,
- ConstructorInfo TypeErrorCtor);
-
// Immutable inputs for language ToString while peer families retain their own declarations.
// Constructed for this body only; never retained as emitter state.
private readonly record struct StringCoercionInputs(
@@ -947,52 +936,6 @@ private void EmitToJsString(TypeBuilder typeBuilder, EmittedStringCoercionRuntim
il.MarkLabel(isObjectLikeLabel);
- // Boxed primitive marker fast-path: if the receiver carries
- // __primitiveType + __primitiveValue (Stage 4z19 wrappers), Stringify
- // the underlying primitive directly. Without this, toString walks the
- // prototype chain to the StringPrototypeGenericStub which doesn't read
- // the marker — returns receiver-as-string instead of the primitive's
- // natural string repr (`new Object(true).valueOf()` gives wrapper, not true).
- var primValLocal = il.DeclareLocal(_types.Object);
- var notBoxedLabel = il.DefineLabel();
- // #574: an own (instance) toString override must win over the boxed
- // __primitiveValue fast-path — ECMA-262 OrdinaryToPrimitive(O, "string")
- // calls the own toString first. When the wrapper carries an own toString,
- // defer to the OrdinaryToPrimitive section below (which invokes it). An
- // inherited prototype toString is NOT own, so un-overridden wrappers still
- // take the fast-path. (HasOwnPropertyHelper does not walk the prototype.)
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Ldstr, "toString");
- il.Emit(OpCodes.Call, peers.HasOwnProperty);
- il.Emit(OpCodes.Brtrue, notBoxedLabel);
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Ldstr, "__primitiveValue");
- il.Emit(OpCodes.Call, peers.GetProperty);
- il.Emit(OpCodes.Stloc, primValLocal);
- il.Emit(OpCodes.Ldloc, primValLocal);
- il.Emit(OpCodes.Brfalse, notBoxedLabel);
- il.Emit(OpCodes.Ldloc, primValLocal);
- il.Emit(OpCodes.Isinst, peers.UndefinedType);
- il.Emit(OpCodes.Brtrue, notBoxedLabel);
- // ECMA-262 §7.1.17 step 2: throw TypeError if the unwrapped primitive
- // is a Symbol. The entry-point check at line ~1856 only catches raw
- // Symbol values — Object(Symbol("x")) wraps it as $Object with
- // __primitiveValue=sym, and the unwrap below bypasses that guard,
- // letting Stringify run on a Symbol (returns "Symbol(x)" rather than
- // throwing). Required by indexOf/searchstring-tostring-errors et al.
- var unwrapNotSymLabel = il.DefineLabel();
- il.Emit(OpCodes.Ldloc, primValLocal);
- il.Emit(OpCodes.Isinst, peers.SymbolType);
- il.Emit(OpCodes.Brfalse, unwrapNotSymLabel);
- GuestErrorEmitter.ThrowError(il, peers.CreateException, peers.TypeErrorCtor, "Cannot convert a Symbol value to a string");
- il.MarkLabel(unwrapNotSymLabel);
- // Re-enter language ToString for the primitive. This is observably
- // different from debug Stringify for BigInt ("42" versus "42n").
- il.Emit(OpCodes.Ldloc, primValLocal);
- il.Emit(OpCodes.Call, coercion.ToJsString);
- il.Emit(OpCodes.Ret);
- il.MarkLabel(notBoxedLabel);
-
// ECMA-262 7.1.1 ToPrimitive(input, "string"): GetMethod(input, @@toPrimitive)
// takes priority over OrdinaryToPrimitive. Look up Symbol.toPrimitive in the
// value's symbol-dict (compiled mode stores symbol-keyed properties separately
@@ -1124,6 +1067,52 @@ private void EmitToJsString(TypeBuilder typeBuilder, EmittedStringCoercionRuntim
il.Emit(OpCodes.Ret);
il.MarkLabel(afterToPrimSymLabel);
+ // Boxed primitive marker fast-path: if the receiver carries
+ // __primitiveType + __primitiveValue (Stage 4z19 wrappers), Stringify
+ // the underlying primitive directly. Without this, toString walks the
+ // prototype chain to the StringPrototypeGenericStub which doesn't read
+ // the marker — returns receiver-as-string instead of the primitive's
+ // natural string repr (`new Object(true).valueOf()` gives wrapper, not true).
+ var primValLocal = il.DeclareLocal(_types.Object);
+ var notBoxedLabel = il.DefineLabel();
+ // #574: an own (instance) toString override must win over the boxed
+ // __primitiveValue fast-path — ECMA-262 OrdinaryToPrimitive(O, "string")
+ // calls the own toString first. When the wrapper carries an own toString,
+ // defer to the OrdinaryToPrimitive section below (which invokes it). An
+ // inherited prototype toString is NOT own, so un-overridden wrappers still
+ // take the fast-path. (HasOwnPropertyHelper does not walk the prototype.)
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldstr, "toString");
+ il.Emit(OpCodes.Call, peers.HasOwnProperty);
+ il.Emit(OpCodes.Brtrue, notBoxedLabel);
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldstr, "__primitiveValue");
+ il.Emit(OpCodes.Call, peers.GetProperty);
+ il.Emit(OpCodes.Stloc, primValLocal);
+ il.Emit(OpCodes.Ldloc, primValLocal);
+ il.Emit(OpCodes.Brfalse, notBoxedLabel);
+ il.Emit(OpCodes.Ldloc, primValLocal);
+ il.Emit(OpCodes.Isinst, peers.UndefinedType);
+ il.Emit(OpCodes.Brtrue, notBoxedLabel);
+ // ECMA-262 §7.1.17 step 2: throw TypeError if the unwrapped primitive
+ // is a Symbol. The entry-point check at line ~1856 only catches raw
+ // Symbol values — Object(Symbol("x")) wraps it as $Object with
+ // __primitiveValue=sym, and the unwrap below bypasses that guard,
+ // letting Stringify run on a Symbol (returns "Symbol(x)" rather than
+ // throwing). Required by indexOf/searchstring-tostring-errors et al.
+ var unwrapNotSymLabel = il.DefineLabel();
+ il.Emit(OpCodes.Ldloc, primValLocal);
+ il.Emit(OpCodes.Isinst, peers.SymbolType);
+ il.Emit(OpCodes.Brfalse, unwrapNotSymLabel);
+ GuestErrorEmitter.ThrowError(il, peers.CreateException, peers.TypeErrorCtor, "Cannot convert a Symbol value to a string");
+ il.MarkLabel(unwrapNotSymLabel);
+ // Re-enter language ToString for the primitive. This is observably
+ // different from debug Stringify for BigInt ("42" versus "42n").
+ il.Emit(OpCodes.Ldloc, primValLocal);
+ il.Emit(OpCodes.Call, coercion.ToJsString);
+ il.Emit(OpCodes.Ret);
+ il.MarkLabel(notBoxedLabel);
+
// emptyArgs = new object[0]
var emptyArgsLocal = il.DeclareLocal(_types.ObjectArray);
il.Emit(OpCodes.Ldc_I4_0);
@@ -1273,22 +1262,8 @@ void TryInvoke(string name, Label afterLabel)
il.Emit(OpCodes.Ret);
}
- private void EmitToNumber(TypeBuilder typeBuilder, EmittedNumericCoercionRuntime numeric,
- AbstractNumberInputs peers)
+ private void EmitNumericToPrimitive(ILGenerator il, AbstractNumberInputs peers, LocalBuilder argLocal)
{
- var method = (MethodBuilder)numeric.ToNumber;
-
- var il = method.GetILGenerator();
- var resultLocal = il.DeclareLocal(_types.Double);
-
- // ECMA-262 7.1.4 ToNumber on object: ToPrimitive(value, "number") which
- // tries valueOf first, then toString. Without this, Math.hypot(obj-with-
- // throwing-valueOf) silently returns NaN instead of propagating the
- // throw. Apply for Dictionary or $Object only.
- var argLocal = il.DeclareLocal(_types.Object);
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Stloc, argLocal);
-
var skipToPrimLabelTop = il.DefineLabel();
var doToPrimLabelTop = il.DefineLabel();
@@ -1540,6 +1515,25 @@ void TryToPrim2(string name, Label afterLabel)
il.MarkLabel(afterTeT);
il.MarkLabel(skipToPrimLabelTop);
+ }
+
+ private void EmitToNumber(TypeBuilder typeBuilder, EmittedNumericCoercionRuntime numeric,
+ AbstractNumberInputs peers)
+ {
+ var method = (MethodBuilder)numeric.ToNumber;
+
+ var il = method.GetILGenerator();
+ var resultLocal = il.DeclareLocal(_types.Double);
+
+ // ECMA-262 7.1.4 ToNumber on object: ToPrimitive(value, "number") which
+ // tries valueOf first, then toString. Without this, Math.hypot(obj-with-
+ // throwing-valueOf) silently returns NaN instead of propagating the
+ // throw. Apply for Dictionary or $Object only.
+ var argLocal = il.DeclareLocal(_types.Object);
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Stloc, argLocal);
+
+ EmitNumericToPrimitive(il, peers, argLocal);
// ECMA-262 7.1.4 ToNumber on Symbol → throws TypeError. Without this,
// Convert.ToDouble would catch the InvalidCastException → NaN → 0,
@@ -1890,7 +1884,7 @@ internal void DeclareConvertToNumber(TypeBuilder typeBuilder, EmittedNumericCoer
}
private void EmitConvertToNumber(TypeBuilder typeBuilder, EmittedNumericCoercionRuntime numeric,
- ExplicitNumberInputs peers)
+ AbstractNumberInputs peers, MethodInfo bigIntToNumber)
{
var method = (MethodBuilder)numeric.ConvertToNumber;
@@ -1914,101 +1908,7 @@ private void EmitConvertToNumber(TypeBuilder typeBuilder, EmittedNumericCoercion
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Stloc, argLocal);
- var skipToPrimLabel = il.DefineLabel();
- var doToPrimLabel = il.DefineLabel();
-
- // Number(array) uses the same Array-to-primitive conversion as the
- // abstract ToNumber operation above.
- var notArrayConvertLabel = il.DefineLabel();
- il.Emit(OpCodes.Ldloc, argLocal);
- il.Emit(OpCodes.Isinst, _types.ListOfObject);
- il.Emit(OpCodes.Brfalse, notArrayConvertLabel);
- il.Emit(OpCodes.Ldloc, argLocal);
- il.Emit(OpCodes.Call, peers.ToJsString);
- il.Emit(OpCodes.Stloc, argLocal);
- il.Emit(OpCodes.Br, skipToPrimLabel);
- il.MarkLabel(notArrayConvertLabel);
-
- il.Emit(OpCodes.Ldloc, argLocal);
- il.Emit(OpCodes.Isinst, _types.DictionaryStringObject);
- il.Emit(OpCodes.Brtrue, doToPrimLabel);
- il.Emit(OpCodes.Ldloc, argLocal);
- il.Emit(OpCodes.Isinst, peers.ObjectType);
- il.Emit(OpCodes.Brfalse, skipToPrimLabel);
- il.MarkLabel(doToPrimLabel);
-
- // ToPrimitive: try valueOf, then toString. Mirrors EmitLengthToPrimitive's
- // logic but writes to argLocal so the existing branches below see the
- // (possibly replaced) primitive value.
- var emptyArgsLocal = il.DeclareLocal(_types.ObjectArray);
- il.Emit(OpCodes.Ldc_I4_0);
- il.Emit(OpCodes.Newarr, _types.Object);
- il.Emit(OpCodes.Stloc, emptyArgsLocal);
-
- void TryToPrim(string name, Label afterLabel)
- {
- var fnLocal = il.DeclareLocal(_types.Object);
- il.Emit(OpCodes.Ldloc, argLocal);
- il.Emit(OpCodes.Ldstr, name);
- il.Emit(OpCodes.Call, peers.GetProperty);
- il.Emit(OpCodes.Stloc, fnLocal);
- il.Emit(OpCodes.Ldloc, fnLocal);
- il.Emit(OpCodes.Brfalse, afterLabel);
- il.Emit(OpCodes.Ldloc, fnLocal);
- il.Emit(OpCodes.Isinst, peers.UndefinedType);
- il.Emit(OpCodes.Brtrue, afterLabel);
-
- var resultLocal = il.DeclareLocal(_types.Object);
- il.Emit(OpCodes.Ldloc, argLocal);
- il.Emit(OpCodes.Ldloc, fnLocal);
- il.Emit(OpCodes.Ldloc, emptyArgsLocal);
- il.Emit(OpCodes.Call, peers.InvokeMethodValue);
- il.Emit(OpCodes.Stloc, resultLocal);
-
- // Still object? Fall through to next attempt without committing.
- il.Emit(OpCodes.Ldloc, resultLocal);
- il.Emit(OpCodes.Isinst, _types.DictionaryStringObject);
- il.Emit(OpCodes.Brtrue, afterLabel);
- il.Emit(OpCodes.Ldloc, resultLocal);
- il.Emit(OpCodes.Isinst, peers.ObjectType);
- il.Emit(OpCodes.Brtrue, afterLabel);
- il.Emit(OpCodes.Ldloc, resultLocal);
- il.Emit(OpCodes.Stloc, argLocal);
- }
-
- var afterValueOf = il.DefineLabel();
- TryToPrim("valueOf", afterValueOf);
- il.MarkLabel(afterValueOf);
-
- var afterToString = il.DefineLabel();
- var stillObj = il.DefineLabel();
- il.Emit(OpCodes.Ldloc, argLocal);
- il.Emit(OpCodes.Isinst, _types.DictionaryStringObject);
- il.Emit(OpCodes.Brtrue, stillObj);
- il.Emit(OpCodes.Ldloc, argLocal);
- il.Emit(OpCodes.Isinst, peers.ObjectType);
- il.Emit(OpCodes.Brfalse, afterToString);
- il.MarkLabel(stillObj);
- TryToPrim("toString", afterToString);
- il.MarkLabel(afterToString);
-
- // ECMA-262 7.1.1.1 OrdinaryToPrimitive: if neither valueOf nor toString
- // returned a primitive (both returned objects, or neither was callable),
- // throw TypeError. Pre-fix: the value fell through to NaN, silently
- // masking the spec-required throw.
- var afterTypeErrorLabel = il.DefineLabel();
- var stillObjAfterToString = il.DefineLabel();
- il.Emit(OpCodes.Ldloc, argLocal);
- il.Emit(OpCodes.Isinst, _types.DictionaryStringObject);
- il.Emit(OpCodes.Brtrue, stillObjAfterToString);
- il.Emit(OpCodes.Ldloc, argLocal);
- il.Emit(OpCodes.Isinst, peers.ObjectType);
- il.Emit(OpCodes.Brfalse, afterTypeErrorLabel);
- il.MarkLabel(stillObjAfterToString);
- GuestErrorEmitter.ThrowError(il, peers.CreateException, peers.TypeErrorCtor, "Cannot convert object to primitive value");
- il.MarkLabel(afterTypeErrorLabel);
-
- il.MarkLabel(skipToPrimLabel);
+ EmitNumericToPrimitive(il, peers, argLocal);
// null => 0.0
il.Emit(OpCodes.Ldloc, argLocal);
@@ -2035,7 +1935,7 @@ void TryToPrim(string name, Label afterLabel)
il.Emit(OpCodes.Brfalse, notBigIntConvLabel);
il.Emit(OpCodes.Ldloc, argLocal);
il.Emit(OpCodes.Unbox_Any, _types.BigInteger);
- il.Emit(OpCodes.Call, peers.BigIntToNumber);
+ il.Emit(OpCodes.Call, bigIntToNumber);
il.Emit(OpCodes.Ret);
il.MarkLabel(notBigIntConvLabel);
diff --git a/src/SharpTS/Compilation/RuntimeEmitter.HasOwnProperty.cs b/src/SharpTS/Compilation/RuntimeEmitter.HasOwnProperty.cs
index 7f7445a5a..801e38dd7 100644
--- a/src/SharpTS/Compilation/RuntimeEmitter.HasOwnProperty.cs
+++ b/src/SharpTS/Compilation/RuntimeEmitter.HasOwnProperty.cs
@@ -730,6 +730,18 @@ PropertyIsEnumerableHelperInputs inputs
il.Emit(OpCodes.Callvirt, _types.GetMethodNoParams(_types.Object, "ToString"));
il.Emit(OpCodes.Stloc, nameLocal);
+ // String exotic objects have a non-enumerable intrinsic length; their
+ // indexed characters still use the ordinary own-property fallback.
+ var notStringLength = il.DefineLabel();
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Isinst, _types.String);
+ il.Emit(OpCodes.Brfalse, notStringLength);
+ il.Emit(OpCodes.Ldloc, nameLocal);
+ il.Emit(OpCodes.Ldstr, "length");
+ il.Emit(OpCodes.Call, _types.GetMethod(_types.String, "op_Equality", _types.String, _types.String));
+ il.Emit(OpCodes.Brtrue, falseLabel);
+ il.MarkLabel(notStringLength);
+
// RegExp lastIndex is an intrinsic own non-enumerable property. It is
// not placed in PDS until user code redefines its attributes.
if (inputs.RegExps.Implementation is not null)
diff --git a/src/SharpTS/Compilation/RuntimeEmitter.MathAdapters.cs b/src/SharpTS/Compilation/RuntimeEmitter.MathAdapters.cs
index e6a6bcea2..1ce5dc4d8 100644
--- a/src/SharpTS/Compilation/RuntimeEmitter.MathAdapters.cs
+++ b/src/SharpTS/Compilation/RuntimeEmitter.MathAdapters.cs
@@ -440,6 +440,14 @@ private MethodBuilder EmitMathSignAdapter(TypeBuilder typeBuilder, MethodInfo to
il.Emit(OpCodes.Ret);
il.MarkLabel(notNaN);
+ var notZero = il.DefineLabel();
+ il.Emit(OpCodes.Ldloc, vLocal);
+ il.Emit(OpCodes.Ldc_R8, 0.0);
+ il.Emit(OpCodes.Bne_Un, notZero);
+ il.Emit(OpCodes.Ldloc, vLocal);
+ il.Emit(OpCodes.Box, _types.Double);
+ il.Emit(OpCodes.Ret);
+ il.MarkLabel(notZero);
il.Emit(OpCodes.Ldloc, vLocal);
il.Emit(OpCodes.Call, _types.GetMethod(_types.Math, "Sign", _types.Double));
il.Emit(OpCodes.Conv_R8);
@@ -550,14 +558,12 @@ private MethodBuilder EmitMathMinMaxAdapter(TypeBuilder typeBuilder, MethodInfo
il.MarkLabel(notNaN);
- // if (isMax ? next > result : next < result) result = next;
- var skipUpdate = il.DefineLabel();
- il.Emit(OpCodes.Ldloc, nextLocal);
+ // Framework Min/Max implement the IEEE signed-zero tie rules as well
+ // as ordinary numeric ordering; a strict comparison loses zero signs.
il.Emit(OpCodes.Ldloc, resultLocal);
- il.Emit(isMax ? OpCodes.Ble : OpCodes.Bge, skipUpdate);
il.Emit(OpCodes.Ldloc, nextLocal);
+ il.Emit(OpCodes.Call, _types.GetMethod(_types.Math, isMax ? "Max" : "Min", _types.Double, _types.Double));
il.Emit(OpCodes.Stloc, resultLocal);
- il.MarkLabel(skipUpdate);
// i++
il.Emit(OpCodes.Ldloc, iLocal);
diff --git a/src/SharpTS/Compilation/RuntimeEmitter.Number.cs b/src/SharpTS/Compilation/RuntimeEmitter.Number.cs
index 15537eabd..f84a6682a 100644
--- a/src/SharpTS/Compilation/RuntimeEmitter.Number.cs
+++ b/src/SharpTS/Compilation/RuntimeEmitter.Number.cs
@@ -778,16 +778,21 @@ private void EmitParseIntHelper(TypeBuilder typeBuilder, EmittedNumberRuntime nu
// Check for 0x prefix (only if radix is 0 or 16)
il.MarkLabel(checkHexLabel);
il.Emit(OpCodes.Ldloc, radixLocal);
- il.Emit(OpCodes.Brtrue, validateRadixLabel); // radix != 0, skip hex detection
+ var stripHexPrefixLabel = il.DefineLabel();
+ il.Emit(OpCodes.Brfalse, stripHexPrefixLabel);
+ il.Emit(OpCodes.Ldloc, radixLocal);
+ il.Emit(OpCodes.Ldc_I4, 16);
+ il.Emit(OpCodes.Bne_Un, validateRadixLabel);
+ il.MarkLabel(stripHexPrefixLabel);
- // radix is 0, check for 0x
+ // A hexadecimal prefix is stripped for both inferred and explicit radix 16.
il.Emit(OpCodes.Ldloc, strLocal);
il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.String, "Length")!.GetGetMethod()!);
il.Emit(OpCodes.Ldloc, startIndexLocal);
il.Emit(OpCodes.Ldc_I4_2);
il.Emit(OpCodes.Add);
var noRoomForHexLabel = il.DefineLabel();
- il.Emit(OpCodes.Ble, noRoomForHexLabel);
+ il.Emit(OpCodes.Blt, noRoomForHexLabel);
// Check if str[startIndex] == '0' && (str[startIndex+1] == 'x' || str[startIndex+1] == 'X')
il.Emit(OpCodes.Ldloc, strLocal);
diff --git a/src/SharpTS/Compilation/RuntimeEmitter.Objects.Descriptors.DefineProperty.cs b/src/SharpTS/Compilation/RuntimeEmitter.Objects.Descriptors.DefineProperty.cs
index 4595f3b73..413f23eef 100644
--- a/src/SharpTS/Compilation/RuntimeEmitter.Objects.Descriptors.DefineProperty.cs
+++ b/src/SharpTS/Compilation/RuntimeEmitter.Objects.Descriptors.DefineProperty.cs
@@ -196,6 +196,28 @@ LocalBuilder propNameLocal
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Isinst, inputs.IHasFieldsInterface);
il.Emit(OpCodes.Brtrue, receiverIsSynthableLabel);
+ // Emitted class constructors expose static fields through System.Type.
+ // Only an own field supplies an existing data descriptor; inherited
+ // fields must retain the defaults for a newly defined own property.
+ var notOwnStaticField = il.DefineLabel();
+ var staticOwner = il.DeclareLocal(_types.Type);
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Isinst, _types.Type);
+ il.Emit(OpCodes.Stloc, staticOwner);
+ il.Emit(OpCodes.Ldloc, staticOwner);
+ il.Emit(OpCodes.Brfalse, notOwnStaticField);
+ il.Emit(OpCodes.Ldtoken, inputs.IHasFieldsInterface);
+ il.Emit(OpCodes.Call, _types.TypeGetTypeFromHandle);
+ il.Emit(OpCodes.Ldloc, staticOwner);
+ il.Emit(OpCodes.Callvirt, _types.GetMethod(_types.Type, "IsAssignableFrom", _types.Type));
+ il.Emit(OpCodes.Brfalse, notOwnStaticField);
+ var staticFieldOwner = EmitStaticMemberLookupOwner(il, staticOwner, inputs.IHasFieldsInterface);
+ il.Emit(OpCodes.Ldloc, staticFieldOwner);
+ il.Emit(OpCodes.Ldloc, propNameLocal);
+ il.Emit(OpCodes.Ldc_I4, (int)(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly));
+ il.Emit(OpCodes.Callvirt, _types.GetMethod(_types.Type, "GetField", _types.String, typeof(BindingFlags)));
+ il.Emit(OpCodes.Brtrue, receiverIsSynthableLabel);
+ il.MarkLabel(notOwnStaticField);
var checkSynthListLabel = il.DefineLabel();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Isinst, inputs.ArrayStorage.Type);
diff --git a/src/SharpTS/Compilation/RuntimeEmitter.Objects.SetProperty.cs b/src/SharpTS/Compilation/RuntimeEmitter.Objects.SetProperty.cs
index 0e9b7d41b..f2f60a22d 100644
--- a/src/SharpTS/Compilation/RuntimeEmitter.Objects.SetProperty.cs
+++ b/src/SharpTS/Compilation/RuntimeEmitter.Objects.SetProperty.cs
@@ -1251,6 +1251,7 @@ void EmitTypeSetSkipName(string n)
il.Emit(OpCodes.Ret);
il.MarkLabel(newTypeDescriptorLabel);
+ EmitRejectInheritedStaticDataWrite(il, inputs.DescriptorStorage, inputs.IHasFieldsInterface, inputs.Errors, strict: false);
var noComputedSetter = il.DefineLabel();
var computedSetter = il.DeclareLocal(_types.Object);
il.Emit(OpCodes.Ldarg_0);
@@ -1911,6 +1912,58 @@ private void EmitToLengthBoxed(ILGenerator il, Type undefinedType)
il.MarkLabel(doneLabel);
}
+ // A writable inherited data property may create an own shadow. A read-only
+ // descriptor must reject that write before either the CLR field bridge or PDS
+ // assignment creates the shadow. Existing own properties take precedence.
+ private void EmitRejectInheritedStaticDataWrite(
+ ILGenerator il, EmittedDescriptorStorageRuntime descriptors, Type fieldsInterface,
+ EmittedErrorRuntime errors, bool strict)
+ {
+ var done = il.DefineLabel();
+ var owner = il.DeclareLocal(_types.Type);
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Isinst, _types.Type);
+ il.Emit(OpCodes.Stloc, owner);
+ il.Emit(OpCodes.Ldloc, owner);
+ il.Emit(OpCodes.Brfalse, done);
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Call, descriptors.GetPropertyDescriptor);
+ il.Emit(OpCodes.Brtrue, done);
+ var fieldOwner = EmitStaticMemberLookupOwner(il, owner, fieldsInterface);
+ il.Emit(OpCodes.Ldloc, fieldOwner);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldc_I4, (int)(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly));
+ il.Emit(OpCodes.Callvirt, _types.GetMethod(_types.Type, "GetField", _types.String, typeof(BindingFlags)));
+ il.Emit(OpCodes.Brtrue, done);
+ var descriptor = il.DeclareLocal(descriptors.DescriptorType);
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Call, descriptors.GetStaticShadow);
+ il.Emit(OpCodes.Stloc, descriptor);
+ il.Emit(OpCodes.Ldloc, descriptor);
+ il.Emit(OpCodes.Brfalse, done);
+ il.Emit(OpCodes.Ldloc, descriptor);
+ il.Emit(OpCodes.Callvirt, descriptors.DescriptorGetter.GetGetMethod()!);
+ il.Emit(OpCodes.Brtrue, done);
+ il.Emit(OpCodes.Ldloc, descriptor);
+ il.Emit(OpCodes.Callvirt, descriptors.DescriptorSetter.GetGetMethod()!);
+ il.Emit(OpCodes.Brtrue, done);
+ il.Emit(OpCodes.Ldloc, descriptor);
+ il.Emit(OpCodes.Callvirt, descriptors.DescriptorWritable.GetGetMethod()!);
+ il.Emit(OpCodes.Brtrue, done);
+ if (strict)
+ {
+ var silent = il.DefineLabel();
+ il.Emit(OpCodes.Ldarg_3);
+ il.Emit(OpCodes.Brfalse, silent);
+ EmitThrowTypeErrorWithName(il, errors, "Cannot assign to read only property '", "' of class");
+ il.MarkLabel(silent);
+ }
+ il.Emit(OpCodes.Ret);
+ il.MarkLabel(done);
+ }
+
///
/// Emits SetPropertyStrict(object obj, string name, object value, bool strictMode) -> void
/// In strict mode, throws TypeError for modifications to frozen objects or new properties on sealed objects.
@@ -2026,10 +2079,11 @@ SetPropertyStrictInputs inputs
il.Emit(OpCodes.Isinst, _types.ListOfObject);
il.Emit(OpCodes.Brtrue, arraySetStrictLabel);
- // Class bodies are strict. Generic static data-field writes must use the
+ // Class bodies are strict. Emitted static data-field writes must use the
// same storage/shadow path as value-position writes outside the class.
- // Restrict this bridge to emitted generic classes with a reflected data
+ // Restrict this bridge to emitted classes with a reflected data
// field; other receiver kinds retain their dedicated assignment paths.
+ EmitRejectInheritedStaticDataWrite(il, inputs.DescriptorStorage, inputs.IHasFieldsInterface, inputs.Errors, strict: true);
var notGenericStaticField = il.DefineLabel();
var genericOwner = il.DeclareLocal(_types.Type);
il.Emit(OpCodes.Ldarg_0);
@@ -2037,9 +2091,6 @@ SetPropertyStrictInputs inputs
il.Emit(OpCodes.Stloc, genericOwner);
il.Emit(OpCodes.Ldloc, genericOwner);
il.Emit(OpCodes.Brfalse, notGenericStaticField);
- il.Emit(OpCodes.Ldloc, genericOwner);
- il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.Type, "ContainsGenericParameters")!.GetGetMethod()!);
- il.Emit(OpCodes.Brfalse, notGenericStaticField);
il.Emit(OpCodes.Ldtoken, inputs.IHasFieldsInterface);
il.Emit(OpCodes.Call, _types.TypeGetTypeFromHandle);
il.Emit(OpCodes.Ldloc, genericOwner);
diff --git a/src/SharpTS/Compilation/RuntimeEmitter.Objects.Utilities.cs b/src/SharpTS/Compilation/RuntimeEmitter.Objects.Utilities.cs
index be0d6c62e..b95ba079f 100644
--- a/src/SharpTS/Compilation/RuntimeEmitter.Objects.Utilities.cs
+++ b/src/SharpTS/Compilation/RuntimeEmitter.Objects.Utilities.cs
@@ -48,6 +48,7 @@ EmittedSymbolRuntime Symbols
// Scoped exact-sum construction inputs; peer declarations stay with their own families.
private readonly record struct MathSumInputs(
+ EmittedArrayStorageRuntime Arrays,
MethodInfo GetSymbolDict,
FieldInfo SymbolIterator,
MethodInfo GetIteratorFunction,
@@ -757,6 +758,9 @@ private void EmitMathSumPrecise(TypeBuilder typeBuilder, EmittedMathRuntime math
math.SumPrecise = method;
var il = method.GetILGenerator();
+ // Numeric-mode arrays inherit List