Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion SharpTS.Test262/baselines/compiled.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Test262 baseline — do not hand-edit. Regenerate with SHARPTS_TEST262_UPDATE_BASELINE=1.
test/built-ins/Array/15.4.5-1.js Pass
test/built-ins/Array/15.4.5.1-5-1.js RuntimeError
test/built-ins/Array/15.4.5.1-5-2.js TypeCheckError
test/built-ins/Array/15.4.5.1-5-2.js RuntimeError
test/built-ins/Array/S15.4.1_A1.1_T1.js Pass
test/built-ins/Array/S15.4.1_A1.1_T2.js Fail
test/built-ins/Array/S15.4.1_A1.1_T3.js Pass
Expand Down
2 changes: 1 addition & 1 deletion SharpTS.Test262/baselines/interpreted.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Test262 baseline — do not hand-edit. Regenerate with SHARPTS_TEST262_UPDATE_BASELINE=1.
test/built-ins/Array/15.4.5-1.js Pass
test/built-ins/Array/15.4.5.1-5-1.js RuntimeError
test/built-ins/Array/15.4.5.1-5-2.js TypeCheckError
test/built-ins/Array/15.4.5.1-5-2.js RuntimeError
test/built-ins/Array/S15.4.1_A1.1_T1.js RuntimeError
test/built-ins/Array/S15.4.1_A1.1_T2.js RuntimeError
test/built-ins/Array/S15.4.1_A1.1_T3.js Pass
Expand Down
65 changes: 65 additions & 0 deletions SharpTS.Tests/TypeCheckerTests/TypeErrorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -617,4 +617,69 @@ function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
}

#endregion

#region Array index range (ECMA-262)

// ECMA-262 array indices are integers in [0, 2^32 - 2]. Numeric literals
// outside that range (e.g. 4294967295, -1) are regular property
// assignments per spec, not array-element writes — so the element-type
// check must not fire for them. Regression for issue #77.

[Fact]
public void StringAssignToOutOfRangeUint32Index_OnNumberArray_Allowed()
{
var source = """
var a: number[] = [0, 1, 2];
a[4294967295] = "spec-legal";
""";

// Type-checker must not reject; runtime may still reject (separate
// sparse-array layer of work).
var ex = Record.Exception(() => TestHarness.RunInterpreted(source));
Assert.False(
ex is not null && ex.Message.Contains("Type Error"),
$"unexpected type error: {ex?.Message}");
}

[Fact]
public void StringAssignToNegativeIndex_OnNumberArray_Allowed()
{
var source = """
var a: number[] = [0, 1, 2];
a[-1] = "not an array element";
""";

var ex = Record.Exception(() => TestHarness.RunInterpreted(source));
Assert.False(
ex is not null && ex.Message.Contains("Type Error"),
$"unexpected type error: {ex?.Message}");
}

[Fact]
public void StringAssignToInRangeIndex_OnNumberArray_StillFails()
{
var source = """
var a: number[] = [0, 1, 2];
a[5] = "still wrong";
""";

var ex = Assert.ThrowsAny<Exception>(() => TestHarness.RunInterpreted(source));
Assert.Contains("Type Error", ex.Message);
Assert.Contains("array of", ex.Message);
}

[Fact]
public void StringAssignToMaxValidIndex_OnNumberArray_StillFails()
{
var source = """
var a: number[] = [0, 1, 2];
a[4294967294] = "still wrong";
""";

var ex = Assert.ThrowsAny<Exception>(() => TestHarness.RunInterpreted(source));
Assert.Contains("Type Error", ex.Message);
Assert.Contains("array of", ex.Message);
}

#endregion
}
31 changes: 30 additions & 1 deletion TypeSystem/TypeChecker.Properties.Index.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,12 @@ private TypeInfo CheckSetIndex(Expr.SetIndex setIndex)

if (objType is TypeInfo.Array arrayType)
{
if (!IsCompatible(arrayType.ElementType, valueType))
// ECMA-262 array indices are integers in [0, 2^32 - 2]. Numeric
// literals outside that range (e.g. 4294967295, -1) are regular
// property assignments, not array-element writes — element-type
// compatibility doesn't apply.
if (IsArrayIndexInRange(setIndex.Index)
&& !IsCompatible(arrayType.ElementType, valueType))
{
throw new TypeCheckException($" Cannot assign '{valueType}' to array of '{arrayType.ElementType}'.");
}
Expand Down Expand Up @@ -553,6 +558,9 @@ or TypeInfo.WeakMap or TypeInfo.WeakSet or TypeInfo.Promise or TypeInfo.Function
{
if (objType is TypeInfo.Array arrayType)
{
// Same out-of-range carve-out as CheckSetIndex above.
if (!IsArrayIndexInRange(setIndex.Index))
return valueType;
if (IsCompatible(arrayType.ElementType, valueType))
return valueType;
return null;
Expand Down Expand Up @@ -587,4 +595,25 @@ or TypeInfo.WeakMap or TypeInfo.WeakSet or TypeInfo.Promise or TypeInfo.Function

return null;
}

/// <summary>
/// Returns true if the index expression is either non-literal (so the
/// strict element-type check should still apply) or is a numeric literal
/// that falls in the ECMA-262 array-index range [0, 2^32 - 2]. Numeric
/// literals outside that range are spec-equivalent to ordinary property
/// assignments and do not write into an array element slot.
/// </summary>
private static bool IsArrayIndexInRange(Expr indexExpr)
{
if (TryGetNumericLiteral(indexExpr) is not double n) return true;
return n >= 0 && n < (double)uint.MaxValue && n == Math.Floor(n);
}

private static double? TryGetNumericLiteral(Expr e)
{
if (e is Expr.Literal { Value: double d }) return d;
if (e is Expr.Unary u && u.Operator.Type == TokenType.MINUS
&& u.Right is Expr.Literal { Value: double d2 }) return -d2;
return null;
}
}
Loading