Skip to content

Commit 0b3544b

Browse files
feat: Improved type conversion error messages
1 parent 23affea commit 0b3544b

File tree

3 files changed

+92
-14
lines changed

3 files changed

+92
-14
lines changed

bridge/jsb_object_bindings.cpp

+11-5
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,9 @@ namespace jsb
228228
: !TypeConvert::js_to_gd_var(isolate, context, info[index], args[index]))
229229
{
230230
// revert all constructors
231-
const String error_message = jsb_errorf("bad argument: %d", index);
231+
const String error_message = index < known_argc
232+
? jsb_errorf("Bad argument: %d. Unable to convert JS %s to Godot %s", index, TypeConvert::js_debug_typeof(isolate, info[index]), Variant::get_type_name(method_info.argument_types[index]))
233+
: jsb_errorf("Bad argument: %d. Unable to convert JS %s", index, TypeConvert::js_debug_typeof(isolate, info[index]));
232234
while (index >= 0) { args[index--].~Variant(); }
233235
impl::Helper::throw_error(isolate, error_message);
234236
return;
@@ -251,7 +253,8 @@ namespace jsb
251253
info.GetReturnValue().Set(jrval);
252254
return;
253255
}
254-
jsb_throw(isolate, "failed to translate godot variant to v8 value");
256+
const String error_message = jsb_errorf("Failed to translate returned Godot %s to a JS value", Variant::get_type_name(crval.get_type()));
257+
impl::Helper::throw_error(isolate, error_message);
255258
}
256259

257260
void ObjectReflectBindingUtil::_godot_object_method(const v8::FunctionCallbackInfo<v8::Value>& info)
@@ -294,7 +297,7 @@ namespace jsb
294297
if (!TypeConvert::js_to_gd_var(isolate, context, info[index], type, args[index]))
295298
{
296299
// revert all constructors
297-
const String error_message = jsb_errorf("bad argument: %d", index);
300+
const String error_message = jsb_errorf("Failed to call: %s. Bad argument: %d. Unable to convert JS %s to Godot %s", method_bind->get_name(), index, TypeConvert::js_debug_typeof(isolate, info[index]), Variant::get_type_name(type));
298301
while (index >= 0) { args[index--].~Variant(); }
299302
impl::Helper::throw_error(isolate, error_message);
300303
return;
@@ -324,7 +327,8 @@ namespace jsb
324327
info.GetReturnValue().Set(jrval);
325328
return;
326329
}
327-
jsb_throw(isolate, "failed to translate godot variant to v8 value");
330+
const String error_message = jsb_errorf("Failed to return from call: %s. Failed to translate returned Godot %s to a JS value", method_bind->get_name(), index, Variant::get_type_name(crval.get_type()));
331+
impl::Helper::throw_error(isolate, error_message);
328332
}
329333

330334
void ObjectReflectBindingUtil::_godot_object_get2(const v8::FunctionCallbackInfo<v8::Value>& info)
@@ -369,7 +373,9 @@ namespace jsb
369373
info.GetReturnValue().Set(jrval);
370374
return;
371375
}
372-
jsb_throw(isolate, "failed to translate godot variant to v8 value");
376+
const String error_message = jsb_errorf("Failed to get property: %s. Failed to translate returned Godot %s to a JS value",
377+
property_info.getter_func->get_name(), index, Variant::get_type_name(crval.get_type()));
378+
impl::Helper::throw_error(isolate, error_message);
373379
}
374380

375381
void ObjectReflectBindingUtil::_godot_object_set2(const v8::FunctionCallbackInfo<v8::Value>& info)

bridge/jsb_type_convert.cpp

+74-9
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,63 @@ namespace jsb
8181
#endif
8282
}
8383

84+
String TypeConvert::js_debug_typeof(v8::Isolate* isolate, const v8::Local<v8::Value>& p_jval)
85+
{
86+
if (p_jval.IsEmpty())
87+
{
88+
return "<empty>";
89+
}
90+
if (p_jval->IsUndefined())
91+
{
92+
return "undefined";
93+
}
94+
if (p_jval->IsNull())
95+
{
96+
return "null";
97+
}
98+
if (p_jval->IsBoolean())
99+
{
100+
return "boolean";
101+
}
102+
if (p_jval->IsNumber())
103+
{
104+
return "number";
105+
}
106+
if (p_jval->IsString())
107+
{
108+
return "string";
109+
}
110+
if (p_jval->IsObject())
111+
{
112+
v8::Local<v8::Object> object = p_jval.As<v8::Object>();
113+
114+
if (is_variant(object))
115+
{
116+
const Variant* variant = (Variant*) object->GetAlignedPointerFromInternalField(IF_Pointer);
117+
return jsb_format("variant (%s)", Variant::get_type_name(variant->get_type()));
118+
}
119+
120+
v8::Local<v8::String> constructor_name = object->GetConstructorName();
121+
122+
if (constructor_name.IsEmpty())
123+
{
124+
return "object";
125+
}
126+
127+
return jsb_format("object (%s)", impl::Helper::to_string(isolate, constructor_name));
128+
}
129+
if (p_jval->IsFunction())
130+
{
131+
return "function";
132+
}
133+
if (p_jval->IsArray())
134+
{
135+
return "array";
136+
}
137+
138+
return "<unknown>";
139+
}
140+
84141
// translate js val into gd variant with an expected type
85142
bool TypeConvert::js_to_gd_var(v8::Isolate* isolate, const v8::Local<v8::Context>& context, const v8::Local<v8::Value>& p_jval, Variant::Type p_type, Variant& r_cvar)
86143
{
@@ -92,15 +149,15 @@ namespace jsb
92149
r_cvar = p_jval.As<v8::Number>()->Value();
93150
return true;
94151
}
95-
return false;
152+
break;
96153
case Variant::INT:
97154
// strict?
98155
if (int64_t val; impl::Helper::to_int64(p_jval, val))
99156
{
100157
r_cvar = val;
101158
return true;
102159
}
103-
return false;
160+
break;
104161
case Variant::OBJECT:
105162
{
106163
if (!p_jval->IsObject())
@@ -111,12 +168,13 @@ namespace jsb
111168
r_cvar = (Object*) nullptr;
112169
return true;
113170
}
114-
return false;
171+
break;
115172
}
116173
const v8::Local<v8::Object> self = p_jval.As<v8::Object>();
174+
117175
if (!TypeConvert::is_object(self))
118176
{
119-
return false;
177+
break;
120178
}
121179

122180
void* pointer = self->GetAlignedPointerFromInternalField(IF_Pointer);
@@ -125,8 +183,12 @@ namespace jsb
125183
}
126184
case Variant::BOOL:
127185
// strict?
128-
if (p_jval->IsBoolean()) { r_cvar = p_jval->BooleanValue(isolate); return true; }
129-
return false;
186+
if (p_jval->IsBoolean())
187+
{
188+
r_cvar = p_jval->BooleanValue(isolate);
189+
return true;
190+
}
191+
break;
130192
case Variant::STRING:
131193
if (p_jval->IsString())
132194
{
@@ -139,7 +201,7 @@ namespace jsb
139201
r_cvar = impl::Helper::to_string(isolate, p_jval);
140202
return true;
141203
}
142-
return false;
204+
break;
143205
case Variant::STRING_NAME:
144206
// cache the JSValue and StringName pair because the expected type is StringName
145207
if (p_jval->IsString())
@@ -199,12 +261,12 @@ namespace jsb
199261
if (!p_jval->IsObject())
200262
{
201263
//TODO should auto convert a null/undefined value to a default (variant) counterpart?
202-
return false;
264+
break;
203265
}
204266
const v8::Local<v8::Object> self = p_jval.As<v8::Object>();
205267
if (!is_variant(self))
206268
{
207-
return false;
269+
break;
208270
}
209271

210272
void* pointer = self->GetAlignedPointerFromInternalField(IF_Pointer);
@@ -216,6 +278,9 @@ namespace jsb
216278
return js_to_gd_var(isolate, context, p_jval, r_cvar);
217279
default: return false;
218280
}
281+
282+
JSB_LOG(Warning, "Failed to convert JS variable to Variant. Expected: %s. Found: %s", Variant::get_type_name(p_type), js_debug_typeof(isolate, p_jval));
283+
return false;
219284
}
220285

221286
bool TypeConvert::gd_var_to_js(v8::Isolate* isolate, const v8::Local<v8::Context>& context, const Variant& p_cvar, Variant::Type p_type, v8::Local<v8::Value>& r_jval)

bridge/jsb_type_convert.h

+7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ namespace jsb
99
{
1010
struct TypeConvert
1111
{
12+
/**
13+
* Returns a string representation of a JavaScript type. For primitives, equivalent to the typeof operator in
14+
* JS. For JS objects, we perform additional inspections and display if the object is an array, the Godot
15+
* variant type, or the constructor name, when available.
16+
*/
17+
static String js_debug_typeof(v8::Isolate* isolate, const v8::Local<v8::Value>& p_jval);
18+
1219
/**
1320
* Translate a Godot object into a javascript object. The type of `p_object_obj` will be automatically exposed to the context if not existed.
1421
* @param p_godot_obj non-null godot object pointer

0 commit comments

Comments
 (0)