Repository: robertkrimen/otto
Pull request #482 (2022-12-06 23:37:54)

chore: improve error messages
Improve error messages so we don't just get TypeError with no clue what the issue was.


Patch:
diff --git a/builtin_array.go b/builtin_array.go
index 2744bca..a82ed01 100644
--- a/builtin_array.go
+++ b/builtin_array.go
@@ -52,7 +52,7 @@ func builtinArrayToLocaleString(call FunctionCall) Value {
 			obj := call.runtime.toObject(value)
 			toLocaleString := obj.get("toLocaleString")
 			if !toLocaleString.isCallable() {
-				panic(call.runtime.panicTypeError())
+				panic(call.runtime.panicTypeError("Array.toLocaleString index[%d] %q is not callable", index, toLocaleString))
 			}
 			stringValue = toLocaleString.call(call.runtime, objectValue(obj)).string()
 		}
@@ -457,7 +457,7 @@ func builtinArraySort(call FunctionCall) Value {
 	compare := compareValue.object()
 	if compareValue.IsUndefined() {
 	} else if !compareValue.isCallable() {
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Array.sort value %q is not callable", compareValue))
 	}
 	if length > 1 {
 		arraySortQuickSort(thisObject, 0, length-1, compare)
@@ -541,7 +541,7 @@ func builtinArrayEvery(call FunctionCall) Value {
 		}
 		return trueValue
 	}
-	panic(call.runtime.panicTypeError())
+	panic(call.runtime.panicTypeError("Array.every argument %q is not callable", call.Argument(0)))
 }
 
 func builtinArraySome(call FunctionCall) Value {
@@ -559,7 +559,7 @@ func builtinArraySome(call FunctionCall) Value {
 		}
 		return falseValue
 	}
-	panic(call.runtime.panicTypeError())
+	panic(call.runtime.panicTypeError("Array.some %q if not callable", call.Argument(0)))
 }
 
 func builtinArrayForEach(call FunctionCall) Value {
@@ -575,7 +575,7 @@ func builtinArrayForEach(call FunctionCall) Value {
 		}
 		return Value{}
 	}
-	panic(call.runtime.panicTypeError())
+	panic(call.runtime.panicTypeError("Array.foreach %q if not callable", call.Argument(0)))
 }
 
 func builtinArrayMap(call FunctionCall) Value {
@@ -594,7 +594,7 @@ func builtinArrayMap(call FunctionCall) Value {
 		}
 		return objectValue(call.runtime.newArrayOf(values))
 	}
-	panic(call.runtime.panicTypeError())
+	panic(call.runtime.panicTypeError("Array.foreach %q if not callable", call.Argument(0)))
 }
 
 func builtinArrayFilter(call FunctionCall) Value {
@@ -614,7 +614,7 @@ func builtinArrayFilter(call FunctionCall) Value {
 		}
 		return objectValue(call.runtime.newArrayOf(values))
 	}
-	panic(call.runtime.panicTypeError())
+	panic(call.runtime.panicTypeError("Array.filter %q if not callable", call.Argument(0)))
 }
 
 func builtinArrayReduce(call FunctionCall) Value {
@@ -647,7 +647,7 @@ func builtinArrayReduce(call FunctionCall) Value {
 			return accumulator
 		}
 	}
-	panic(call.runtime.panicTypeError())
+	panic(call.runtime.panicTypeError("Array.reduce %q if not callable", call.Argument(0)))
 }
 
 func builtinArrayReduceRight(call FunctionCall) Value {
@@ -679,5 +679,5 @@ func builtinArrayReduceRight(call FunctionCall) Value {
 			return accumulator
 		}
 	}
-	panic(call.runtime.panicTypeError())
+	panic(call.runtime.panicTypeError("Array.reduceRight %q if not callable", call.Argument(0)))
 }
diff --git a/builtin_date.go b/builtin_date.go
index 3ee81b0..2d1d601 100644
--- a/builtin_date.go
+++ b/builtin_date.go
@@ -81,7 +81,7 @@ func builtinDateToJSON(call FunctionCall) Value {
 	toISOString := obj.get("toISOString")
 	if !toISOString.isCallable() {
 		// FIXME
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Date.toJSON toISOString %q is not callable", toISOString))
 	}
 	return toISOString.call(call.runtime, objectValue(obj), []Value{})
 }
diff --git a/builtin_error.go b/builtin_error.go
index 7e043d7..9d524f5 100644
--- a/builtin_error.go
+++ b/builtin_error.go
@@ -15,7 +15,7 @@ func builtinNewError(obj *object, argumentList []Value) Value {
 func builtinErrorToString(call FunctionCall) Value {
 	thisObject := call.thisObject()
 	if thisObject == nil {
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Error.toString is nil"))
 	}
 
 	name := classErrorName
diff --git a/builtin_function.go b/builtin_function.go
index 6ae1ae1..51d2cac 100644
--- a/builtin_function.go
+++ b/builtin_function.go
@@ -58,14 +58,14 @@ func builtinFunctionToString(call FunctionCall) Value {
 		return stringValue(fn.node.source)
 	case bindFunctionObject:
 		return stringValue("function () { [native code] }")
+	default:
+		panic(call.runtime.panicTypeError("Function.toString unknown type %T", obj.value))
 	}
-
-	panic(call.runtime.panicTypeError("Function.toString()"))
 }
 
 func builtinFunctionApply(call FunctionCall) Value {
 	if !call.This.isCallable() {
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Function.apply %q is not callable", call.This))
 	}
 	this := call.Argument(0)
 	if this.IsUndefined() {
@@ -78,7 +78,7 @@ func builtinFunctionApply(call FunctionCall) Value {
 		return call.thisObject().call(this, nil, false, nativeFrame)
 	case valueObject:
 	default:
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Function.apply unknown type %T for second argument"))
 	}
 
 	arrayObject := argumentList.object()
@@ -93,7 +93,7 @@ func builtinFunctionApply(call FunctionCall) Value {
 
 func builtinFunctionCall(call FunctionCall) Value {
 	if !call.This.isCallable() {
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Function.call %q is not callable", call.This))
 	}
 	thisObject := call.thisObject()
 	this := call.Argument(0)
@@ -110,7 +110,7 @@ func builtinFunctionCall(call FunctionCall) Value {
 func builtinFunctionBind(call FunctionCall) Value {
 	target := call.This
 	if !target.isCallable() {
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Function.bind %q is not callable", call.This))
 	}
 	targetObject := target.object()
 
diff --git a/builtin_json.go b/builtin_json.go
index 9d59c44..960c393 100644
--- a/builtin_json.go
+++ b/builtin_json.go
@@ -180,12 +180,12 @@ func builtinJSONStringify(call FunctionCall) Value {
 	}
 	valueJSON, err := json.Marshal(value)
 	if err != nil {
-		panic(call.runtime.panicTypeError(err.Error()))
+		panic(call.runtime.panicTypeError("JSON.stringify marshal: %s", err))
 	}
 	if ctx.gap != "" {
 		valueJSON1 := bytes.Buffer{}
 		if err = json.Indent(&valueJSON1, valueJSON, "", ctx.gap); err != nil {
-			panic(call.runtime.panicTypeError(err.Error()))
+			panic(call.runtime.panicTypeError("JSON.stringify indent: %s", err))
 		}
 		valueJSON = valueJSON1.Bytes()
 	}
diff --git a/builtin_object.go b/builtin_object.go
index 6d7408c..029a75f 100644
--- a/builtin_object.go
+++ b/builtin_object.go
@@ -81,7 +81,7 @@ func builtinObjectToString(call FunctionCall) Value {
 func builtinObjectToLocaleString(call FunctionCall) Value {
 	toString := call.thisObject().get("toString")
 	if !toString.isCallable() {
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Object.toLocaleString %q is not callable", toString))
 	}
 	return toString.call(call.runtime, call.This)
 }
@@ -90,7 +90,7 @@ func builtinObjectGetPrototypeOf(call FunctionCall) Value {
 	val := call.Argument(0)
 	obj := val.object()
 	if obj == nil {
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Object.GetPrototypeOf is nil"))
 	}
 
 	if obj.prototype == nil {
@@ -104,7 +104,7 @@ func builtinObjectGetOwnPropertyDescriptor(call FunctionCall) Value {
 	val := call.Argument(0)
 	obj := val.object()
 	if obj == nil {
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Object.GetOwnPropertyDescriptor is nil"))
 	}
 
 	name := call.Argument(1).string()
@@ -119,7 +119,7 @@ func builtinObjectDefineProperty(call FunctionCall) Value {
 	val := call.Argument(0)
 	obj := val.object()
 	if obj == nil {
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Object.DefineProperty is nil"))
 	}
 	name := call.Argument(1).string()
 	descriptor := toPropertyDescriptor(call.runtime, call.Argument(2))
@@ -131,7 +131,7 @@ func builtinObjectDefineProperties(call FunctionCall) Value {
 	val := call.Argument(0)
 	obj := val.object()
 	if obj == nil {
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Object.DefineProperties is nil"))
 	}
 
 	properties := call.runtime.toObject(call.Argument(1))
@@ -147,7 +147,7 @@ func builtinObjectDefineProperties(call FunctionCall) Value {
 func builtinObjectCreate(call FunctionCall) Value {
 	prototypeValue := call.Argument(0)
 	if !prototypeValue.IsNull() && !prototypeValue.IsObject() {
-		panic(call.runtime.panicTypeError())
+		panic(call.runtime.panicTypeError("Object.Create is nil"))
 	}
 
 	obj := call.runtime.newObject()
@@ -171,17 +171,16 @@ func builtinObjectIsExtensible(call FunctionCall) Value {
 	if obj := val.object(); obj != nil {
 		return boolValue(obj.extensible)
 	}
-	panic(call.runtime.panicTypeError())
+	panic(call.runtime.panicTypeError("Object.IsExtensible is nil"))
 }
 
 func builtinObjectPreventExtensions(call FunctionCall) Value {
 	val := call.Argument(0)
 	if obj := val.object(); obj != nil {
 		obj.extensible = false
-	} else {
-		panic(call.runtime.panicTypeError())
+		return val
 	}
-	return val
+	panic(call.runtime.panicTypeError("Object.PreventExtensions is nil"))
 }
 
 func builtinObjectIsSealed(call FunctionCall) Value {
@@ -200,7 +199,7 @@ func builtinObjectIsSealed(call FunctionCall) Value {
 		})
 		return boolValue(result)
 	}
-	panic(call.runtime.panicTypeError())
+	panic(call.runtime.panicTypeError("Object.IsSealed is nil"))
 }
 
 func builtinObjectSeal(call FunctionCall) Value {
@@ -214,10 +213,9 @@ func builtinObjectSeal(call FunctionCall) Value {
 			return true
 		})
 		obj.extensible = false
-	} else {
-		panic(call.runtime.panicTypeError())
+		return val
 	}
-	return val
+	panic(call.runtime.panicTypeError("Object.Seal is nil"))
 }
 
 func builtinObjectIsFrozen(call FunctionCall) Value {
@@ -236,7 +234,7 @@ func builtinObjectIsFrozen(call FunctionCall) Value {
 		})
 		return boolValue(result)
 	}
-	panic(call.runtime.panicTypeError())
+	panic(call.runtime.panicTypeError("Object.IsFrozen is nil"))
 }
 
 func builtinObjectFreeze(call FunctionCall) Value {
@@ -259,10 +257,9 @@ func builtinObjectFreeze(call FunctionCall) Value {
 			return true
 		})
 		obj.extensible = false
-	} else {
-		panic(call.runtime.panicTypeError())
+		return val
 	}
-	return val
+	panic(call.runtime.panicTypeError("Object.Freeze is nil"))
 }
 
 func builtinObjectKeys(call FunctionCall) Value {
@@ -273,7 +270,7 @@ func builtinObjectKeys(call FunctionCall) Value {
 		})
 		return objectValue(call.runtime.newArrayOf(keys))
 	}
-	panic(call.runtime.panicTypeError())
+	panic(call.runtime.panicTypeError("Object.Keys is nil"))
 }
 
 func builtinObjectGetOwnPropertyNames(call FunctionCall) Value {
diff --git a/cmpl_evaluate_expression.go b/cmpl_evaluate_expression.go
index 27964dc..69c673a 100644
--- a/cmpl_evaluate_expression.go
+++ b/cmpl_evaluate_expression.go
@@ -169,7 +169,7 @@ func (rt *runtime) cmplEvaluateNodeBracketExpression(node *nodeBracketExpression
 	// TODO Pass in base value as-is, and defer toObject till later?
 	obj, err := rt.objectCoerce(targetValue)
 	if err != nil {
-		panic(rt.panicTypeError("Cannot access member '%s' of %s", memberValue.string(), err.Error(), at(node.idx)))
+		panic(rt.panicTypeError("Cannot access member %q of %s", memberValue.string(), err, at(node.idx)))
 	}
 	return toValue(newPropertyReference(rt, obj, memberValue.string(), false, at(node.idx)))
 }
@@ -201,7 +201,7 @@ func (rt *runtime) cmplEvaluateNodeCallExpression(node *nodeCallExpression, with
 			eval = rf.name == "eval" // Possible direct eval
 		default:
 			// FIXME?
-			panic(rt.panicTypeError("Here be dragons"))
+			panic(rt.panicTypeError("unexpected callee type %T to node call expression", rf))
 		}
 	}
 
@@ -226,7 +226,7 @@ func (rt *runtime) cmplEvaluateNodeCallExpression(node *nodeCallExpression, with
 			// FIXME Maybe typeof?
 			panic(rt.panicTypeError("%v is not a function", vl, atv))
 		}
-		panic(rt.panicTypeError("'%s' is not a function", name, atv))
+		panic(rt.panicTypeError("%q is not a function", name, atv))
 	}
 
 	rt.scope.frame.offset = int(atv)
@@ -249,7 +249,7 @@ func (rt *runtime) cmplEvaluateNodeDotExpression(node *nodeDotExpression) Value
 	// TODO Pass in base value as-is, and defer toObject till later?
 	obj, err := rt.objectCoerce(targetValue)
 	if err != nil {
-		panic(rt.panicTypeError("Cannot access member '%s' of %s", node.identifier, err.Error(), at(node.idx)))
+		panic(rt.panicTypeError("Cannot access member %q of %s", node.identifier, err, at(node.idx)))
 	}
 	return toValue(newPropertyReference(rt, obj, node.identifier, false, at(node.idx)))
 }
@@ -270,7 +270,7 @@ func (rt *runtime) cmplEvaluateNodeNewExpression(node *nodeNewExpression) Value
 		case *stashReference:
 			name = rf.name
 		default:
-			panic(rt.panicTypeError("Here be dragons"))
+			panic(rt.panicTypeError("node new expression unexpected callee type %T", rf))
 		}
 	}
 
diff --git a/evaluate.go b/evaluate.go
index aba7276..2108b7c 100644
--- a/evaluate.go
+++ b/evaluate.go
@@ -119,14 +119,14 @@ func (rt *runtime) calculateBinaryExpression(operator token.Token, left Value, r
 	case token.INSTANCEOF:
 		rightValue := right.resolve()
 		if !rightValue.IsObject() {
-			panic(rt.panicTypeError("Expecting a function in instanceof check, but got: %v", rightValue))
+			panic(rt.panicTypeError("invalid kind %s for instanceof (expected object)", rightValue.kind))
 		}
 		return boolValue(rightValue.object().hasInstance(leftValue))
 
 	case token.IN:
 		rightValue := right.resolve()
 		if !rightValue.IsObject() {
-			panic(rt.panicTypeError())
+			panic(rt.panicTypeError("invalid kind %s for in (expected object)", rightValue.kind))
 		}
 		return boolValue(rightValue.object().hasProperty(leftValue.string()))
 	}
diff --git a/generate.go b/generate.go
index e1b069b..767091e 100644
--- a/generate.go
+++ b/generate.go
@@ -1,3 +1,4 @@
 package otto
 
 //go:generate go run ./tools/gen-jscore -output inline.go
+//go:generate stringer -type=valueKind -trimprefix=value -output=value_kind.gen.go
diff --git a/object_class.go b/object_class.go
index c1a59bb..a9cfbf6 100644
--- a/object_class.go
+++ b/object_class.go
@@ -210,7 +210,7 @@ func objectCanPutDetails(obj *object, name string) (canPut bool, prop *property,
 			canPut = setter != nil
 			return
 		default:
-			panic(obj.runtime.panicTypeError())
+			panic(obj.runtime.panicTypeError("unexpected type %T to Object.CanPutDetails", prop.value))
 		}
 	}
 
@@ -234,7 +234,7 @@ func objectCanPutDetails(obj *object, name string) (canPut bool, prop *property,
 		canPut = setter != nil
 		return
 	default:
-		panic(obj.runtime.panicTypeError())
+		panic(obj.runtime.panicTypeError("unexpected type %T to Object.CanPutDetails", prop.value))
 	}
 }
 
@@ -282,22 +282,23 @@ func objectPut(obj *object, name string, value Value, throw bool) {
 			}
 		}
 		obj.defineProperty(name, value, 0o111, throw)
-	} else {
-		switch propertyValue := prop.value.(type) {
-		case Value:
-			prop.value = value
-			obj.defineOwnProperty(name, *prop, throw)
-		case propertyGetSet:
-			if propertyValue[1] != nil {
-				propertyValue[1].call(toValue(obj), []Value{value}, false, nativeFrame)
-				return
-			}
-			if throw {
-				panic(obj.runtime.panicTypeError())
-			}
-		default:
-			panic(obj.runtime.panicTypeError())
+		return
+	}
+
+	switch propertyValue := prop.value.(type) {
+	case Value:
+		prop.value = value
+		obj.defineOwnProperty(name, *prop, throw)
+	case propertyGetSet:
+		if propertyValue[1] != nil {
+			propertyValue[1].call(toValue(obj), []Value{value}, false, nativeFrame)
+			return
+		}
+		if throw {
+			panic(obj.runtime.panicTypeError("Object.Put nil second parameter to propertyGetSet"))
 		}
+	default:
+		panic(obj.runtime.panicTypeError("Object.Put unexpected type %T", prop.value))
 	}
 }
 
@@ -312,9 +313,9 @@ func objectHasOwnProperty(obj *object, name string) bool {
 
 // 8.12.9.
 func objectDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
-	reject := func() bool {
+	reject := func(reason string) bool {
 		if throw {
-			panic(obj.runtime.panicTypeError())
+			panic(obj.runtime.panicTypeError("Object.DefineOwnProperty: %s", reason))
 		}
 		return false
 	}
@@ -322,7 +323,7 @@ func objectDefineOwnProperty(obj *object, name string, descriptor property, thro
 	prop, exists := obj.readProperty(name)
 	if !exists {
 		if !obj.extensible {
-			return reject()
+			return reject("not exists and not extensible")
 		}
 		if newGetSet, isAccessor := descriptor.value.(propertyGetSet); isAccessor {
 			if newGetSet[0] == &nilGetSetObject {
@@ -347,12 +348,12 @@ func objectDefineOwnProperty(obj *object, name string, descriptor property, thro
 	configurable := prop.configurable()
 	if !configurable {
 		if descriptor.configurable() {
-			return reject()
+			return reject("property and descriptor not configurable")
 		}
 		// Test that, if enumerable is set on the property descriptor, then it should
 		// be the same as the existing property
 		if descriptor.enumerateSet() && descriptor.enumerable() != prop.enumerable() {
-			return reject()
+			return reject("property not configurable and enumerable miss match")
 		}
 	}
 
@@ -364,17 +365,17 @@ func objectDefineOwnProperty(obj *object, name string, descriptor property, thro
 	case isDataDescriptor != descriptor.isDataDescriptor():
 		// DataDescriptor <=> AccessorDescriptor
 		if !configurable {
-			return reject()
+			return reject("property descriptor not configurable")
 		}
 	case isDataDescriptor && descriptor.isDataDescriptor():
 		// DataDescriptor <=> DataDescriptor
 		if !configurable {
 			if !prop.writable() && descriptor.writable() {
-				return reject()
+				return reject("property not configurable or writeable and descriptor not writeable")
 			}
 			if !prop.writable() {
 				if descriptor.value != nil && !sameValue(value, descriptor.value.(Value)) {
-					return reject()
+					return reject("property not configurable or writeable and descriptor not the same")
 				}
 			}
 		}
@@ -400,7 +401,7 @@ func objectDefineOwnProperty(obj *object, name string, descriptor property, thro
 		}
 		if !configurable {
 			if (presentGet && (getSet[0] != newGetSet[0])) || (presentSet && (getSet[1] != newGetSet[1])) {
-				return reject()
+				return reject("access descriptor not configurable")
 			}
 		}
 		descriptor.value = newGetSet
diff --git a/property.go b/property.go
index 8fa7c1c..9c2758e 100644
--- a/property.go
+++ b/property.go
@@ -117,7 +117,7 @@ func (p property) isEmpty() bool {
 func toPropertyDescriptor(rt *runtime, value Value) property {
 	objectDescriptor := value.object()
 	if objectDescriptor == nil {
-		panic(rt.panicTypeError())
+		panic(rt.panicTypeError("toPropertyDescriptor on nil"))
 	}
 
 	var descriptor property
@@ -153,7 +153,7 @@ func toPropertyDescriptor(rt *runtime, value Value) property {
 		value := objectDescriptor.get("get")
 		if value.IsDefined() {
 			if !value.isCallable() {
-				panic(rt.panicTypeError())
+				panic(rt.panicTypeError("toPropertyDescriptor get not callable"))
 			}
 			getter = value.object()
 			getterSetter = true
@@ -167,7 +167,7 @@ func toPropertyDescriptor(rt *runtime, value Value) property {
 		value := objectDescriptor.get("set")
 		if value.IsDefined() {
 			if !value.isCallable() {
-				panic(rt.panicTypeError())
+				panic(rt.panicTypeError("toPropertyDescriptor set not callable"))
 			}
 			setter = value.object()
 			getterSetter = true
@@ -179,14 +179,14 @@ func toPropertyDescriptor(rt *runtime, value Value) property {
 
 	if getterSetter {
 		if descriptor.writeSet() {
-			panic(rt.panicTypeError())
+			panic(rt.panicTypeError("toPropertyDescriptor descriptor writeSet"))
 		}
 		descriptor.value = propertyGetSet{getter, setter}
 	}
 
 	if objectDescriptor.hasProperty("value") {
 		if getterSetter {
-			panic(rt.panicTypeError())
+			panic(rt.panicTypeError("toPropertyDescriptor value getterSetter"))
 		}
 		descriptor.value = objectDescriptor.get("value")
 	}
diff --git a/runtime.go b/runtime.go
index cbe1857..883b512 100644
--- a/runtime.go
+++ b/runtime.go
@@ -147,7 +147,7 @@ func (rt *runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, isExcep
 func (rt *runtime) toObject(value Value) *object {
 	switch value.kind {
 	case valueEmpty, valueUndefined, valueNull:
-		panic(rt.panicTypeError())
+		panic(rt.panicTypeError("toObject unsupported kind %s", value.kind))
 	case valueBoolean:
 		return rt.newBoolean(value)
 	case valueString:
@@ -157,7 +157,7 @@ func (rt *runtime) toObject(value Value) *object {
 	case valueObject:
 		return value.object()
 	default:
-		panic(rt.panicTypeError())
+		panic(rt.panicTypeError("toObject unknown kind %s", value.kind))
 	}
 }
 
@@ -176,19 +176,18 @@ func (rt *runtime) objectCoerce(value Value) (*object, error) {
 	case valueObject:
 		return value.object(), nil
 	default:
-		panic(rt.panicTypeError())
+		panic(rt.panicTypeError("objectCoerce unknown kind %s", value.kind))
 	}
 }
 
 func checkObjectCoercible(rt *runtime, value Value) {
 	isObject, mustCoerce := testObjectCoercible(value)
 	if !isObject && !mustCoerce {
-		panic(rt.panicTypeError())
+		panic(rt.panicTypeError("checkObjectCoercible not object or mustCoerce"))
 	}
 }
 
-// testObjectCoercible
-
+// testObjectCoercible.
 func testObjectCoercible(value Value) (isObject, mustCoerce bool) { //nolint: nonamedreturns
 	switch value.kind {
 	case valueReference, valueEmpty, valueNull, valueUndefined:
@@ -198,7 +197,7 @@ func testObjectCoercible(value Value) (isObject, mustCoerce bool) { //nolint: no
 	case valueObject:
 		return true, false
 	default:
-		panic("this should never happen")
+		panic(fmt.Sprintf("testObjectCoercible unknown kind %s", value.kind))
 	}
 }
 
@@ -534,7 +533,7 @@ func (rt *runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Value,
 
 				r, err := rt.convertCallParameter(rv, t.Out(0))
 				if err != nil {
-					panic(rt.panicTypeError(err.Error()))
+					panic(rt.panicTypeError("convertCallParameter Func: %s", err))
 				}
 
 				return []reflect.Value{r}
diff --git a/stash.go b/stash.go
index fcbb717..40aeb85 100644
--- a/stash.go
+++ b/stash.go
@@ -194,7 +194,7 @@ func (s *dclStash) getBinding(name string, throw bool) Value {
 	}
 	if !prop.mutable && !prop.readable {
 		if throw { // strict?
-			panic(s.rt.panicTypeError())
+			panic(s.rt.panicTypeError("getBinding property %s not mutable and not readable", name))
 		}
 		return Value{}
 	}
diff --git a/type_array.go b/type_array.go
index 0907d38..cb67354 100644
--- a/type_array.go
+++ b/type_array.go
@@ -55,6 +55,13 @@ func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw
 	if !valid {
 		panic("Array.length != Value{}")
 	}
+
+	reject := func(reason string) bool {
+		if throw {
+			panic(obj.runtime.panicTypeError("Array.DefineOwnProperty %s", reason))
+		}
+		return false
+	}
 	length := lengthValue.value.(uint32)
 	if name == propertyLength {
 		if descriptor.value == nil {
@@ -62,7 +69,7 @@ func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw
 		}
 		newLengthValue, isValue := descriptor.value.(Value)
 		if !isValue {
-			panic(obj.runtime.panicTypeError())
+			panic(obj.runtime.panicTypeError("Array.DefineOwnProperty %q is not a value", descriptor.value))
 		}
 		newLength := arrayUint32(obj.runtime, newLengthValue)
 		descriptor.value = uint32Value(newLength)
@@ -70,7 +77,7 @@ func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw
 			return objectDefineOwnProperty(obj, name, descriptor, throw)
 		}
 		if !lengthProperty.writable() {
-			goto Reject
+			return reject("property length for not writable")
 		}
 		newWritable := true
 		if descriptor.mode&0o700 == 0 {
@@ -89,7 +96,7 @@ func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw
 					descriptor.mode &= 0o077
 				}
 				objectDefineOwnProperty(obj, name, descriptor, false)
-				goto Reject
+				return reject("delete failed")
 			}
 		}
 		if !newWritable {
@@ -98,10 +105,10 @@ func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw
 		}
 	} else if index := stringToArrayIndex(name); index >= 0 {
 		if index >= int64(length) && !lengthProperty.writable() {
-			goto Reject
+			return reject("property length not writable")
 		}
 		if !objectDefineOwnProperty(obj, strconv.FormatInt(index, 10), descriptor, false) {
-			goto Reject
+			return reject("Object.DefineOwnProperty failed")
 		}
 		if index >= int64(length) {
 			lengthProperty.value = uint32Value(uint32(index + 1))
@@ -110,9 +117,4 @@ func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw
 		}
 	}
 	return objectDefineOwnProperty(obj, name, descriptor, throw)
-Reject:
-	if throw {
-		panic(obj.runtime.panicTypeError())
-	}
-	return false
 }
diff --git a/type_date.go b/type_date.go
index d5482e2..ac95676 100644
--- a/type_date.go
+++ b/type_date.go
@@ -140,8 +140,11 @@ func (o *object) dateValue() dateObject {
 }
 
 func dateObjectOf(rt *runtime, date *object) dateObject {
-	if date == nil || date.class != classDateName {
-		panic(rt.panicTypeError())
+	if date == nil {
+		panic(rt.panicTypeError("Date.ObjectOf is nil"))
+	}
+	if date.class != classDateName {
+		panic(rt.panicTypeError("Date.ObjectOf %q != %q", date.class, classDateName))
 	}
 	return date.dateValue()
 }
diff --git a/type_function.go b/type_function.go
index 28f6f9b..bd40770 100644
--- a/type_function.go
+++ b/type_function.go
@@ -107,8 +107,9 @@ func (fn bindFunctionObject) construct(argumentList []Value) Value {
 	case nodeFunctionObject:
 		argumentList = append(fn.argumentList, argumentList...)
 		return obj.construct(argumentList)
+	default:
+		panic(fn.target.runtime.panicTypeError("construct unknown type %T", obj.value))
 	}
-	panic(fn.target.runtime.panicTypeError())
 }
 
 // nodeFunctionObject.
@@ -246,14 +247,14 @@ func (o *object) construct(argumentList []Value) Value {
 func (o *object) hasInstance(of Value) bool {
 	if !o.isCall() {
 		// We should not have a hasInstance method
-		panic(o.runtime.panicTypeError())
+		panic(o.runtime.panicTypeError("Object.hasInstance not callable"))
 	}
 	if !of.IsObject() {
 		return false
 	}
 	prototype := o.get("prototype")
 	if !prototype.IsObject() {
-		panic(o.runtime.panicTypeError())
+		panic(o.runtime.panicTypeError("Object.hasInstance prototype %q is not an object", prototype))
 	}
 	prototypeObject := prototype.object()
 
@@ -306,7 +307,7 @@ func (f *FunctionCall) thisObject() *object {
 
 func (f *FunctionCall) thisClassObject(class string) *object {
 	if o := f.thisObject(); o.class != class {
-		panic(f.runtime.panicTypeError())
+		panic(f.runtime.panicTypeError("Function.Class %s != %s", o.class, class))
 	}
 	return f.thisObj
 }
diff --git a/type_go_struct.go b/type_go_struct.go
index 8cae329..e191c57 100644
--- a/type_go_struct.go
+++ b/type_go_struct.go
@@ -69,7 +69,7 @@ func (o goStructObject) setValue(rt *runtime, name string, value Value) bool {
 	fieldValue := o.getValue(name)
 	converted, err := rt.convertCallParameter(value, fieldValue.Type())
 	if err != nil {
-		panic(rt.panicTypeError(err.Error()))
+		panic(rt.panicTypeError("Object.setValue convertCallParameter: %s", err))
 	}
 	fieldValue.Set(converted)
 
diff --git a/value.go b/value.go
index 521e08a..1f86e14 100644
--- a/value.go
+++ b/value.go
@@ -118,10 +118,7 @@ func (v Value) call(rt *runtime, this Value, argumentList ...interface{}) Value
 	if function, ok := v.value.(*object); ok {
 		return function.call(this, function.runtime.toValueArray(argumentList...), false, nativeFrame)
 	}
-	if rt == nil {
-		panic("FIXME TypeError")
-	}
-	panic(rt.panicTypeError())
+	panic(rt.panicTypeError("call %q is not an object", v.value))
 }
 
 func (v Value) constructSafe(rt *runtime, this Value, argumentList ...interface{}) (Value, error) {
@@ -136,10 +133,7 @@ func (v Value) construct(rt *runtime, this Value, argumentList ...interface{}) V
 	if fn, ok := v.value.(*object); ok {
 		return fn.construct(fn.runtime.toValueArray(argumentList...))
 	}
-	if rt == nil {
-		panic("FIXME TypeError")
-	}
-	panic(rt.panicTypeError())
+	panic(rt.panicTypeError("construct %q is not an object", v.value))
 }
 
 // IsPrimitive will return true if value is a primitive (any kind of primitive).
diff --git a/value_kind.gen.go b/value_kind.gen.go
new file mode 100644
index 0000000..cf99c0a
--- /dev/null
+++ b/value_kind.gen.go
@@ -0,0 +1,31 @@
+// Code generated by "stringer -type=valueKind -trimprefix=value -output=value_kind.gen.go"; DO NOT EDIT.
+
+package otto
+
+import "strconv"
+
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[valueUndefined-0]
+	_ = x[valueNull-1]
+	_ = x[valueNumber-2]
+	_ = x[valueString-3]
+	_ = x[valueBoolean-4]
+	_ = x[valueObject-5]
+	_ = x[valueEmpty-6]
+	_ = x[valueResult-7]
+	_ = x[valueReference-8]
+}
+
+const _valueKind_name = "UndefinedNullNumberStringBooleanObjectEmptyResultReference"
+
+var _valueKind_index = [...]uint8{0, 9, 13, 19, 25, 32, 38, 43, 49, 58}
+
+func (i valueKind) String() string {
+	if i < 0 || i >= valueKind(len(_valueKind_index)-1) {
+		return "valueKind(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _valueKind_name[_valueKind_index[i]:_valueKind_index[i+1]]
+}


Test patch:
diff --git a/.github/workflows/test-lint.yml b/.github/workflows/test-lint.yml
index 263201c..f56b026 100644
--- a/.github/workflows/test-lint.yml
+++ b/.github/workflows/test-lint.yml
@@ -25,6 +25,7 @@ jobs:
       - name: Validate go mod / generate
         run: |
           go mod tidy
+          go install golang.org/x/tools/cmd/stringer@latest
           go generate ./...
           git --no-pager diff && [[ 0 -eq $(git status --porcelain | wc -l) ]]
 
diff --git a/array_test.go b/array_test.go
index 602f341..1a33a86 100644
--- a/array_test.go
+++ b/array_test.go
@@ -111,7 +111,7 @@ func TestArray_toLocaleString(t *testing.T) {
 
 		test(`raise:
             [ { toLocaleString: undefined } ].toLocaleString();
-        `, "TypeError")
+        `, `TypeError: Array.toLocaleString index[0] "undefined" is not callable`)
 	})
 }
 
@@ -429,9 +429,9 @@ func TestArray_every(t *testing.T) {
 	tt(t, func() {
 		test, _ := test()
 
-		test(`raise: [].every()`, "TypeError")
+		test(`raise: [].every()`, `TypeError: Array.every argument "undefined" is not callable`)
 
-		test(`raise: [].every("abc")`, "TypeError")
+		test(`raise: [].every("abc")`, `TypeError: Array.every argument "abc" is not callable`)
 
 		test(`[].every(function() { return false })`, true)
 
@@ -479,7 +479,7 @@ func TestArray_some(t *testing.T) {
 	tt(t, func() {
 		test, _ := test()
 
-		test(`raise: [].some("abc")`, "TypeError")
+		test(`raise: [].some("abc")`, `TypeError: Array.some "abc" if not callable`)
 
 		test(`[].some(function() { return true })`, false)
 
@@ -521,7 +521,7 @@ func TestArray_forEach(t *testing.T) {
 	tt(t, func() {
 		test, _ := test()
 
-		test(`raise: [].forEach("abc")`, "TypeError")
+		test(`raise: [].forEach("abc")`, `TypeError: Array.foreach "abc" if not callable`)
 
 		test(`
             var abc = 0;
@@ -594,7 +594,7 @@ func TestArray_map(t *testing.T) {
 	tt(t, func() {
 		test, _ := test()
 
-		test(`raise: [].map("abc")`, "TypeError")
+		test(`raise: [].map("abc")`, `TypeError: Array.foreach "abc" if not callable`)
 
 		test(`[].map(function() { return 1 }).length`, 0)
 
@@ -626,7 +626,7 @@ func TestArray_filter(t *testing.T) {
 	tt(t, func() {
 		test, _ := test()
 
-		test(`raise: [].filter("abc")`, "TypeError")
+		test(`raise: [].filter("abc")`, `TypeError: Array.filter "abc" if not callable`)
 
 		test(`[].filter(function() { return 1 }).length`, 0)
 
@@ -640,9 +640,9 @@ func TestArray_reduce(t *testing.T) {
 	tt(t, func() {
 		test, _ := test()
 
-		test(`raise: [].reduce("abc")`, "TypeError")
+		test(`raise: [].reduce("abc")`, `TypeError: Array.reduce "abc" if not callable`)
 
-		test(`raise: [].reduce(function() {})`, "TypeError")
+		test(`raise: [].reduce(function() {})`, `TypeError: Array.reduce "function() {}" if not callable`)
 
 		test(`[].reduce(function() {}, 0)`, 0)
 
@@ -660,9 +660,9 @@ func TestArray_reduceRight(t *testing.T) {
 	tt(t, func() {
 		test, _ := test()
 
-		test(`raise: [].reduceRight("abc")`, "TypeError")
+		test(`raise: [].reduceRight("abc")`, `TypeError: Array.reduceRight "abc" if not callable`)
 
-		test(`raise: [].reduceRight(function() {})`, "TypeError")
+		test(`raise: [].reduceRight(function() {})`, `TypeError: Array.reduceRight "function() {}" if not callable`)
 
 		test(`[].reduceRight(function() {}, 0)`, 0)
 
@@ -697,7 +697,7 @@ func TestArray_defineOwnProperty(t *testing.T) {
             Object.defineProperty(abc, "length", {
                 writable: true
             });
-        `, "TypeError")
+        `, `TypeError: Object.DefineOwnProperty: property not configurable or writeable and descriptor not writeable`)
 	})
 }
 
diff --git a/error_test.go b/error_test.go
index 1a40c88..27b627f 100644
--- a/error_test.go
+++ b/error_test.go
@@ -80,7 +80,7 @@ func TestErrorContext(t *testing.T) {
         `)
 		{
 			err := asError(t, err)
-			is(err.message, "'undefined' is not a function")
+			is(err.message, `"undefined" is not a function`)
 			is(len(err.trace), 1)
 			is(err.trace[0].location(), "<anonymous>:2:13")
 		}
@@ -90,7 +90,7 @@ func TestErrorContext(t *testing.T) {
         `)
 		{
 			err := asError(t, err)
-			is(err.message, "'abc' is not a function")
+			is(err.message, `"abc" is not a function`)
 			is(len(err.trace), 1)
 			is(err.trace[0].location(), "<anonymous>:2:14")
 		}
@@ -100,7 +100,7 @@ func TestErrorContext(t *testing.T) {
         `)
 		{
 			err := asError(t, err)
-			is(err.message, "'abc' is not a function")
+			is(err.message, `"abc" is not a function`)
 			is(len(err.trace), 1)
 			is(err.trace[0].location(), "<anonymous>:2:14")
 		}
@@ -111,7 +111,7 @@ func TestErrorContext(t *testing.T) {
         `)
 		{
 			err := asError(t, err)
-			is(err.message, "'ghi' is not a function")
+			is(err.message, `"ghi" is not a function`)
 			is(len(err.trace), 1)
 			is(err.trace[0].location(), "<anonymous>:3:13")
 		}
@@ -127,7 +127,7 @@ func TestErrorContext(t *testing.T) {
         `)
 		{
 			err := asError(t, err)
-			is(err.message, "'undefined' is not a function")
+			is(err.message, `"undefined" is not a function`)
 			is(len(err.trace), 3)
 			is(err.trace[0].location(), "def (<anonymous>:3:17)")
 			is(err.trace[1].location(), "abc (<anonymous>:6:17)")
@@ -174,7 +174,7 @@ func TestErrorContext(t *testing.T) {
         `)
 		{
 			err := asError(t, err)
-			is(err.message, "'xyzzy' is not a function")
+			is(err.message, `"xyzzy" is not a function`)
 			is(len(err.trace), 1)
 			is(err.trace[0].location(), "<anonymous>:1:1")
 		}
@@ -251,7 +251,7 @@ func TestErrorContext(t *testing.T) {
 			f, _ := vm.Get("C")
 			_, err := f.Call(UndefinedValue())
 			err1 := asError(t, err)
-			is(err1.message, "Cannot access member 'prop' of null")
+			is(err1.message, `Cannot access member "prop" of null`)
 			is(len(err1.trace), 1)
 			is(err1.trace[0].location(), "C (file1.js:7:5)")
 		}
diff --git a/function_test.go b/function_test.go
index 030bac3..28c64e6 100644
--- a/function_test.go
+++ b/function_test.go
@@ -229,7 +229,7 @@ func TestFunction_bind(t *testing.T) {
 
 		test(`raise:
             Math.bind();
-        `, "TypeError: 'bind' is not a function")
+        `, `TypeError: "bind" is not a function`)
 
 		test(`
             function construct(fn, arguments) {
@@ -268,7 +268,7 @@ func TestFunction_toString(t *testing.T) {
 
 		test(`raise:
             Function.prototype.toString.call(undefined);
-        `, "TypeError")
+        `, "TypeError: Function.Class environment != Function")
 
 		test(`
             abc = function()   {       return -1    ;
diff --git a/object_test.go b/object_test.go
index 8f806d5..3af0188 100644
--- a/object_test.go
+++ b/object_test.go
@@ -63,7 +63,7 @@ func TestObject_create(t *testing.T) {
 	tt(t, func() {
 		test, _ := test()
 
-		test(`raise: Object.create()`, "TypeError")
+		test(`raise: Object.create()`, "TypeError: Object.Create is nil")
 
 		test(`
             var abc = Object.create(null)
@@ -121,7 +121,7 @@ func TestObject_isExtensible(t *testing.T) {
 
 		test(`raise:
             Object.isExtensible();
-        `, "TypeError")
+        `, "TypeError: Object.IsExtensible is nil")
 
 		// FIXME terst, Why raise?
 		test(`raise:
@@ -139,7 +139,7 @@ func TestObject_preventExtensions(t *testing.T) {
 
 		test(`raise:
             Object.preventExtensions()
-        `, "TypeError")
+        `, "TypeError: Object.PreventExtensions is nil")
 
 		test(`raise:
             var abc = { def: true };
@@ -178,7 +178,7 @@ func TestObject_seal(t *testing.T) {
 	tt(t, func() {
 		test, _ := test()
 
-		test(`raise: Object.seal()`, "TypeError")
+		test(`raise: Object.seal()`, "TypeError: Object.Seal is nil")
 
 		test(`
             var abc = {a:1,b:1,c:3};
@@ -211,7 +211,7 @@ func TestObject_isFrozen(t *testing.T) {
 	tt(t, func() {
 		test, _ := test()
 
-		test(`raise: Object.isFrozen()`, "TypeError")
+		test(`raise: Object.isFrozen()`, "TypeError: Object.IsFrozen is nil")
 		test(`Object.isFrozen(Object.preventExtensions({a:1}))`, false)
 		test(`Object.isFrozen({})`, false)
 
@@ -235,7 +235,7 @@ func TestObject_freeze(t *testing.T) {
 	tt(t, func() {
 		test, _ := test()
 
-		test(`raise: Object.freeze()`, "TypeError")
+		test(`raise: Object.freeze()`, "TypeError: Object.Freeze is nil")
 
 		test(`
             var abc = {a:1,b:2,c:3};
@@ -395,7 +395,7 @@ func TestObjectGetterSetter(t *testing.T) {
                     writable: true
                 }
             }).abc;
-        `, "TypeError")
+        `, "TypeError: toPropertyDescriptor descriptor writeSet")
 
 		test(`raise:
             Object.create({}, {
@@ -406,7 +406,7 @@ func TestObjectGetterSetter(t *testing.T) {
                     writable: false
                 }
             }).abc;
-        `, "TypeError")
+        `, "TypeError: toPropertyDescriptor descriptor writeSet")
 
 		test(`
             Object.create({}, {
@@ -512,7 +512,7 @@ func TestObjectGetterSetter(t *testing.T) {
                     return 2;
                 }
             });
-        `, "TypeError")
+        `, `TypeError: Array.DefineOwnProperty ["function () {\n                    return 2;\n                }" <nil>] is not a value`)
 
 		test(`
             var abc = {};
diff --git a/panic_test.go b/panic_test.go
index 06f0a64..43da046 100644
--- a/panic_test.go
+++ b/panic_test.go
@@ -21,7 +21,7 @@ func Test_panic(t *testing.T) {
             var abc = [];
             Object.defineProperty(abc, "0", { writable: false });
             Object.defineProperty(abc, "0", { value: false, writable: false });
-        `, "TypeError")
+        `, "TypeError: Array.DefineOwnProperty Object.DefineOwnProperty failed")
 
 		// Test that a regular expression can contain \c0410 (CYRILLIC CAPITAL LETTER A)
 		// without panicking
diff --git a/runtime_test.go b/runtime_test.go
index 7036f64..d7b861e 100644
--- a/runtime_test.go
+++ b/runtime_test.go
@@ -627,11 +627,11 @@ func Test_instanceof(t *testing.T) {
 
 		test(`raise:
             abc = {} instanceof "abc";
-        `, "TypeError: Expecting a function in instanceof check, but got: abc")
+        `, "TypeError: invalid kind String for instanceof (expected object)")
 
 		test(`raise:
             "xyzzy" instanceof Math;
-        `, "TypeError")
+        `, "TypeError: Object.hasInstance not callable")
 	})
 }