From 9eb646e008f12519bc5b0271c4b828fbcefce62c Mon Sep 17 00:00:00 2001 From: Cody Mullins <1738479+codymullins@users.noreply.github.com> Date: Tue, 16 Jun 2026 17:09:54 -0400 Subject: [PATCH 1/2] test(js): match negative type Test262Error by constructor name The Test262 runner classified a negative test's thrown error by reading the instance's name property. Built-in errors expose their type that way (TypeError.prototype.name and friends), but the harness Test262Error is a plain function that sets only message, so its instances have no name. Every test with 'negative type: Test262Error' was therefore scored as a failure (expected Test262Error, threw Error) even though the engine threw the right error and terminated the comment correctly. Fall back to the thrown value's constructor name when the instance has no name, which is what the official test262 runner matches against. The engine was already correct. This removes a harness false negative. language/line-terminators: 82.9% -> 100.0% (68/82 -> 82/82). language/module-code: +2 (the two Test262Error module tests). --- tests/Starling.Js.Test262.Tests/Test262Runner.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/Starling.Js.Test262.Tests/Test262Runner.cs b/tests/Starling.Js.Test262.Tests/Test262Runner.cs index 80e64161..f7950c7b 100644 --- a/tests/Starling.Js.Test262.Tests/Test262Runner.cs +++ b/tests/Starling.Js.Test262.Tests/Test262Runner.cs @@ -369,8 +369,20 @@ private static bool IsParseLevel(Exception ex) => private static string? ErrorName(JsValue value) { if (!value.IsObject) return null; - var n = value.AsObject.Get("name"); - return n.IsString ? n.AsString : null; + var obj = value.AsObject; + // Built-in errors expose their type via `name` (TypeError.prototype.name + // etc.). The harness's Test262Error and other plain error classes set no + // `name` at all, so fall back to the constructor's name — which is what + // the official test262 runner matches a negative `type` against. + var n = obj.Get("name"); + if (n.IsString) return n.AsString; + var ctor = obj.Get("constructor"); + if (ctor.IsObject) + { + var cn = ctor.AsObject.Get("name"); + if (cn.IsString) return cn.AsString; + } + return null; } private static string ExtractMessage(Exception ex) => From 4f8efe415700b5fa2d5440792b1a3ae06879d4c8 Mon Sep 17 00:00:00 2001 From: Cody Mullins <1738479+codymullins@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:45:54 -0400 Subject: [PATCH 2/2] test(js): cover Test262Error constructor-name matching Add unit tests for the runner's ErrorName: a Test262Error instance (no name property) resolves by its constructor name, a built-in error still resolves by name, an own name wins over the constructor name, and a thrown primitive has no error name. ErrorName is relaxed to internal so the test in the same assembly can call it. --- .../Test262Runner.cs | 2 +- .../Test262RunnerErrorNameTests.cs | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/Starling.Js.Test262.Tests/Test262RunnerErrorNameTests.cs diff --git a/tests/Starling.Js.Test262.Tests/Test262Runner.cs b/tests/Starling.Js.Test262.Tests/Test262Runner.cs index f7950c7b..78b48798 100644 --- a/tests/Starling.Js.Test262.Tests/Test262Runner.cs +++ b/tests/Starling.Js.Test262.Tests/Test262Runner.cs @@ -366,7 +366,7 @@ private static bool IsParseLevel(Exception ex) => /// Read the name property of a thrown error value (e.g. /// "TypeError"); null when the throw isn't an error-shaped object. - private static string? ErrorName(JsValue value) + internal static string? ErrorName(JsValue value) { if (!value.IsObject) return null; var obj = value.AsObject; diff --git a/tests/Starling.Js.Test262.Tests/Test262RunnerErrorNameTests.cs b/tests/Starling.Js.Test262.Tests/Test262RunnerErrorNameTests.cs new file mode 100644 index 00000000..02d2c292 --- /dev/null +++ b/tests/Starling.Js.Test262.Tests/Test262RunnerErrorNameTests.cs @@ -0,0 +1,51 @@ +using Starling.Js.Bytecode; +using Starling.Js.Parse; +using Starling.Js.Runtime; + +namespace Starling.Js.Test262.Tests; + +/// +/// Pins how the runner classifies a thrown error against a negative test's +/// type. The harness's Test262Error is a plain function with no +/// name property, so a negative type: Test262Error can only be +/// matched by the thrown value's constructor name. +/// +[TestClass] +public class Test262RunnerErrorNameTests +{ + [TestMethod] + public void Test262Error_instance_resolves_by_constructor_name() + { + var value = Eval("function Test262Error(m){ this.message = m || ''; } new Test262Error('boom');"); + Assert.AreEqual("Test262Error", Test262Runner.ErrorName(value)); + } + + [TestMethod] + public void Builtin_error_resolves_by_name_property() + { + var value = Eval("new TypeError('x');"); + Assert.AreEqual("TypeError", Test262Runner.ErrorName(value)); + } + + [TestMethod] + public void Name_property_wins_over_constructor_name() + { + // `name` is present, so it is used even though the constructor (Object) + // has a different name. + var value = Eval("({ name: 'Custom' });"); + Assert.AreEqual("Custom", Test262Runner.ErrorName(value)); + } + + [TestMethod] + public void Thrown_primitive_has_no_error_name() + { + var value = Eval("'not an error';"); + Assert.IsNull(Test262Runner.ErrorName(value)); + } + + private static JsValue Eval(string source) + { + var chunk = JsCompiler.CompileForEval(new JsParser(source).ParseProgram()); + return new JsVm(new JsRuntime()).Run(chunk); + } +}