From fb9084a36124b6b2e302f9e9777a5e38d716ecef Mon Sep 17 00:00:00 2001 From: "Chris (ChrisJr404)" <11917633+ChrisJr404@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:01:36 -0400 Subject: [PATCH] feat(engine): include the value type in "not a constructor" errors --- core/engine/src/builtins/function/tests.rs | 2 +- .../engine/src/object/internal_methods/mod.rs | 11 +++++-- core/engine/src/tests/function.rs | 29 ++++++++++++++++++- core/engine/src/vm/opcode/new/mod.rs | 10 +++++-- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/core/engine/src/builtins/function/tests.rs b/core/engine/src/builtins/function/tests.rs index 7200a610e3c..e33a7526da0 100644 --- a/core/engine/src/builtins/function/tests.rs +++ b/core/engine/src/builtins/function/tests.rs @@ -62,7 +62,7 @@ fn function_prototype() { TestAction::assert_native_error( "new Function.prototype()", JsNativeErrorKind::Type, - "not a constructor", + "function is not a constructor", ), ]); } diff --git a/core/engine/src/object/internal_methods/mod.rs b/core/engine/src/object/internal_methods/mod.rs index 8e474b5dde5..f09998af056 100644 --- a/core/engine/src/object/internal_methods/mod.rs +++ b/core/engine/src/object/internal_methods/mod.rs @@ -1207,12 +1207,19 @@ fn non_existent_call( } fn non_existent_construct( - _obj: &JsObject, + obj: &JsObject, _argument_count: usize, context: &mut InternalMethodCallContext<'_>, ) -> JsResult { + // A callable object reaching this point is a function without a [[Construct]] + // slot (e.g. an arrow function or method), so report it as a function. + let type_of = if obj.is_callable() { + "function" + } else { + "object" + }; Err(JsNativeError::typ() - .with_message("not a constructor") + .with_message(format!("{type_of} is not a constructor")) .with_realm(context.realm().clone()) .into()) } diff --git a/core/engine/src/tests/function.rs b/core/engine/src/tests/function.rs index 84cb0c8b777..1c7f2be453c 100644 --- a/core/engine/src/tests/function.rs +++ b/core/engine/src/tests/function.rs @@ -215,10 +215,37 @@ fn should_type_error_when_new_is_not_constructor() { run_test_actions([TestAction::assert_native_error( "new ''()", JsNativeErrorKind::Type, - "not a constructor", + "string is not a constructor", )]); } +#[test] +fn new_on_non_constructor_reports_the_value_type() { + run_test_actions([ + TestAction::assert_native_error( + "new 5()", + JsNativeErrorKind::Type, + "number is not a constructor", + ), + TestAction::assert_native_error( + "new undefined()", + JsNativeErrorKind::Type, + "undefined is not a constructor", + ), + TestAction::assert_native_error( + "new true()", + JsNativeErrorKind::Type, + "boolean is not a constructor", + ), + // A callable object without a [[Construct]] slot, such as an arrow function. + TestAction::assert_native_error( + "new (() => {})()", + JsNativeErrorKind::Type, + "function is not a constructor", + ), + ]); +} + #[test] fn new_instance_should_point_to_prototype() { // A new instance should point to a prototype object created with the constructor function diff --git a/core/engine/src/vm/opcode/new/mod.rs b/core/engine/src/vm/opcode/new/mod.rs index d54c7e2bdeb..c4bd5d343b5 100644 --- a/core/engine/src/vm/opcode/new/mod.rs +++ b/core/engine/src/vm/opcode/new/mod.rs @@ -18,7 +18,10 @@ impl New { let cons = func .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("not a constructor"))? + .ok_or_else(|| { + JsNativeError::typ() + .with_message(format!("{} is not a constructor", func.type_of())) + })? .clone(); context.vm.stack.push(cons.clone()); // Push new.target @@ -59,7 +62,10 @@ impl NewSpread { let cons = func .as_object() - .ok_or_else(|| JsNativeError::typ().with_message("not a constructor"))? + .ok_or_else(|| { + JsNativeError::typ() + .with_message(format!("{} is not a constructor", func.type_of())) + })? .clone(); let argument_count = arguments.len();