Skip to content

Commit 3b593dc

Browse files
committed
implement more compat
- Date to NSDate conversion - implicit constructor calling (new NSObject -> NSObject.new(), and parametered constructors matching as well) - return null properly on native->JS conversions
1 parent 2fcfa9f commit 3b593dc

File tree

5 files changed

+236
-24
lines changed

5 files changed

+236
-24
lines changed

NativeScript/ffi/Class.mm

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,25 @@ void setupObjCClassDecorator(napi_env env) {
195195
}
196196

197197
NAPI_FUNCTION(BridgedConstructor) {
198-
NAPI_CALLBACK_BEGIN(0)
198+
NAPI_CALLBACK_BEGIN(1)
199+
200+
napi_valuetype jsType;
201+
napi_typeof(env, argv[0], &jsType);
202+
203+
id object = nil;
204+
205+
if (jsType == napi_external) {
206+
return jsThis;
207+
} else {
208+
Class cls = (Class)data;
209+
object = [cls new];
210+
211+
ObjCBridgeState* bridgeState = ObjCBridgeState::InstanceData(env);
212+
jsThis = bridgeState->proxyNativeObject(env, jsThis, object);
213+
}
214+
215+
napi_wrap(env, jsThis, object, nullptr, nullptr, nullptr);
216+
199217
return jsThis;
200218
}
201219

@@ -390,7 +408,8 @@ napi_value toJS(napi_env env) {
390408
// extended by a JS class.
391409
// Every Bridged Class extends the NativeObject class.
392410

393-
void defineProtocolMembers(napi_env env, ObjCClassMemberMap& members, napi_value constructor, ObjCProtocol* protocol) {
411+
void defineProtocolMembers(napi_env env, ObjCClassMemberMap& members, napi_value constructor,
412+
ObjCProtocol* protocol) {
394413
ObjCClassMember::defineMembers(env, members, protocol->membersOffset, constructor);
395414
for (auto protocol : protocol->protocols) {
396415
defineProtocolMembers(env, members, constructor, protocol);
@@ -443,11 +462,12 @@ void defineProtocolMembers(napi_env env, ObjCClassMemberMap& members, napi_value
443462

444463
napi_value constructor, prototype;
445464

446-
napi_define_class(env, name.c_str(), name.length(), JS_BridgedConstructor, (void*)this, 0, nil,
447-
&constructor);
465+
napi_define_class(env, name.c_str(), name.length(), JS_BridgedConstructor, (void*)nativeClass, 0,
466+
nil, &constructor);
448467

449468
if (nativeClass != nil) {
450469
napi_wrap(env, constructor, (void*)nativeClass, nil, nil, nil);
470+
bridgeState->classesByPointer[nativeClass] = this;
451471
}
452472

453473
napi_get_named_property(env, constructor, "prototype", &prototype);
@@ -552,18 +572,17 @@ void defineProtocolMembers(napi_env env, ObjCClassMemberMap& members, napi_value
552572
return;
553573
}
554574

555-
bridgeState->classesByPointer[nativeClass] = this;
556-
557575
// Add the 'extend' static method to all native classes (not NativeObject)
558576
if (!isNativeObject) {
559577
napi_value extendMethod;
560-
napi_create_function(env, "extend", NAPI_AUTO_LENGTH, ClassBuilder::ExtendCallback, nullptr, &extendMethod);
578+
napi_create_function(env, "extend", NAPI_AUTO_LENGTH, ClassBuilder::ExtendCallback, nullptr,
579+
&extendMethod);
561580
napi_set_named_property(env, constructor, "extend", extendMethod);
562581
}
563582

564583
if (!hasMembers) return;
565584

566-
ObjCClassMember::defineMembers(env, members, offset, constructor);
585+
ObjCClassMember::defineMembers(env, members, offset, constructor, this);
567586
}
568587

569588
ObjCClass::~ObjCClass() {

NativeScript/ffi/ClassMember.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,13 @@ class ObjCClassMember;
4747

4848
typedef std::unordered_map<std::string, ObjCClassMember> ObjCClassMemberMap;
4949

50+
class ObjCClass;
51+
5052
class ObjCClassMember {
5153
public:
5254
static void defineMembers(napi_env env, ObjCClassMemberMap& memberMap,
53-
MDSectionOffset offset, napi_value constructor);
55+
MDSectionOffset offset, napi_value constructor,
56+
ObjCClass* cls = nullptr);
5457

5558
static napi_value jsCall(napi_env env, napi_callback_info cbinfo);
5659
static napi_value jsCallInit(napi_env env, napi_callback_info cbinfo);
@@ -83,6 +86,7 @@ class ObjCClassMember {
8386
Cif* setterCif = nullptr;
8487
bool returnOwned;
8588
bool classMethod;
89+
ObjCClass* cls;
8690
};
8791

8892
} // namespace nativescript

NativeScript/ffi/ClassMember.mm

Lines changed: 164 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "ObjCBridge.h"
99
#include "TypeConv.h"
1010
#include "Util.h"
11+
#include "ffi/Class.h"
1112
#include "ffi/NativeScriptException.h"
1213
#include "js_native_api.h"
1314
#include "js_native_api_types.h"
@@ -35,7 +36,8 @@ napi_value JS_NSObject_alloc(napi_env env, napi_callback_info cbinfo) {
3536
}
3637

3738
void ObjCClassMember::defineMembers(napi_env env, ObjCClassMemberMap& memberMap,
38-
MDSectionOffset offset, napi_value constructor) {
39+
MDSectionOffset offset, napi_value constructor,
40+
ObjCClass* cls) {
3941
auto bridgeState = ObjCBridgeState::InstanceData(env);
4042

4143
napi_value prototype;
@@ -115,7 +117,7 @@ napi_value JS_NSObject_alloc(napi_env env, napi_callback_info cbinfo) {
115117

116118
bool hasProperty = false;
117119
napi_has_named_property(env, jsObject, name.c_str(), &hasProperty);
118-
if (hasProperty) {
120+
if (hasProperty && name != "init") {
119121
continue;
120122
}
121123

@@ -135,6 +137,10 @@ napi_value JS_NSObject_alloc(napi_env env, napi_callback_info cbinfo) {
135137
.data = &kv.first->second,
136138
};
137139

140+
if ((flags & mdMemberIsInit) != 0) {
141+
kv.first->second.cls = cls;
142+
}
143+
138144
if (name == "alloc") {
139145
property.method = JS_NSObject_alloc;
140146
}
@@ -198,6 +204,144 @@ inline bool objcNativeCall(napi_env env, Cif* cif, id self, void** avalues, void
198204
return true;
199205
}
200206

207+
// Utility function to check if a JS value can be converted to a specific type
208+
bool canConvertToType(napi_env env, napi_value value, std::shared_ptr<TypeConv> typeConv) {
209+
if (value == nullptr) {
210+
return true; // null/undefined can convert to most types
211+
}
212+
213+
napi_valuetype jsType;
214+
napi_typeof(env, value, &jsType);
215+
216+
if (jsType == napi_null || jsType == napi_undefined) {
217+
return true; // null/undefined are generally acceptable
218+
}
219+
220+
// Check basic type compatibility based on TypeConv kind
221+
switch (typeConv->kind) {
222+
case mdTypeBool:
223+
return jsType == napi_boolean || jsType == napi_number;
224+
225+
case mdTypeChar:
226+
case mdTypeUChar:
227+
case mdTypeSShort:
228+
case mdTypeUShort:
229+
case mdTypeSInt:
230+
case mdTypeUInt:
231+
case mdTypeSLong:
232+
case mdTypeULong:
233+
case mdTypeSInt64:
234+
case mdTypeUInt64:
235+
case mdTypeFloat:
236+
case mdTypeDouble:
237+
return jsType == napi_number || jsType == napi_bigint;
238+
239+
case mdTypeInstanceObject:
240+
case mdTypeNSStringObject:
241+
case mdTypeNSMutableStringObject: {
242+
if (jsType == napi_string) {
243+
// String can convert to NSString
244+
return true;
245+
}
246+
if (jsType == napi_object) {
247+
bool isArray;
248+
napi_is_array(env, value, &isArray);
249+
if (isArray) {
250+
// Array can convert to NSArray
251+
return true;
252+
}
253+
// Check if it's a wrapped native object
254+
void* wrapped;
255+
napi_status status = napi_unwrap(env, value, &wrapped);
256+
return status == napi_ok;
257+
}
258+
return false;
259+
}
260+
261+
case mdTypeSelector:
262+
return jsType == napi_string;
263+
264+
case mdTypePointer:
265+
case mdTypeOpaquePointer:
266+
return jsType == napi_object || jsType == napi_bigint || jsType == napi_string;
267+
268+
case mdTypeStruct:
269+
return jsType == napi_object;
270+
271+
case mdTypeBlock:
272+
return jsType == napi_function;
273+
274+
default:
275+
return true; // For unknown types, assume compatible
276+
}
277+
}
278+
279+
// Find the best initializer for a class given JS arguments
280+
ObjCClassMember* findInitializerForArgs(napi_env env, ObjCClassMemberMap* initializers, size_t argc,
281+
napi_value* argv) {
282+
std::vector<ObjCClassMember*> candidates;
283+
284+
// First pass: find initializers with matching argument count
285+
for (auto& pair : *initializers) {
286+
auto* candidate = &pair.second;
287+
const char* name = sel_getName(candidate->methodOrGetter.selector);
288+
// NSLog(@"Checking initializer: %s", name);
289+
if (name[0] != 'i' || name[1] != 'n' || name[2] != 'i' || name[3] != 't') {
290+
continue;
291+
}
292+
Cif* cif = candidate->cif;
293+
if (!cif) {
294+
// Need to get the CIF to check argument count
295+
cif = const_cast<ObjCClassMember*>(candidate)->bridgeState->getMethodCif(
296+
env, candidate->methodOrGetter.signatureOffset);
297+
}
298+
299+
// Match argument count (cif->argc excludes self and selector)
300+
if (cif->argc == argc) {
301+
bool canInvoke = true;
302+
303+
// Check if all arguments can be converted to the expected types
304+
for (size_t i = 0; i < argc; ++i) {
305+
if (!canConvertToType(env, argv[i], cif->argTypes[i])) {
306+
canInvoke = false;
307+
break;
308+
}
309+
}
310+
311+
if (canInvoke) {
312+
candidates.push_back(candidate);
313+
}
314+
}
315+
}
316+
317+
if (candidates.empty()) {
318+
napi_throw_error(env, "NativeScriptException",
319+
"No initializer found that matches constructor invocation.");
320+
return nullptr;
321+
} else if (candidates.size() > 1) {
322+
// Prefer "init" if no arguments
323+
if (argc == 0) {
324+
for (auto* candidate : candidates) {
325+
const char* selectorName = sel_getName(candidate->methodOrGetter.selector);
326+
if (strcmp(selectorName, "init") == 0) {
327+
return candidate;
328+
}
329+
}
330+
}
331+
332+
// If multiple candidates, throw an error with details
333+
std::string errorMsg = "More than one initializer found that matches constructor invocation:";
334+
for (const auto* candidate : candidates) {
335+
errorMsg += " ";
336+
errorMsg += sel_getName(candidate->methodOrGetter.selector);
337+
}
338+
napi_throw_error(env, "NativeScriptException", errorMsg.c_str());
339+
return nullptr;
340+
}
341+
342+
return candidates[0];
343+
}
344+
201345
inline id assertSelf(napi_env env, napi_value jsThis) {
202346
id self;
203347
napi_unwrap(env, jsThis, (void**)&self);
@@ -216,21 +360,37 @@ inline id assertSelf(napi_env env, napi_value jsThis) {
216360
napi_value jsThis;
217361
ObjCClassMember* method;
218362

219-
napi_get_cb_info(env, cbinfo, nullptr, nullptr, &jsThis, (void**)&method);
363+
size_t argc = 0;
364+
napi_get_cb_info(env, cbinfo, &argc, nullptr, &jsThis, (void**)&method);
220365

221366
id self = assertSelf(env, jsThis);
222367

223368
if (self == nullptr) {
224369
return nullptr;
225370
}
226371

372+
SEL sel = method->methodOrGetter.selector;
373+
if (sel == @selector(init) && argc > 0) {
374+
napi_value argv[argc];
375+
napi_get_cb_info(env, cbinfo, &argc, argv, &jsThis, nullptr);
376+
Class nativeClass = [self class];
377+
ObjCBridgeState* state = ObjCBridgeState::InstanceData(env);
378+
ObjCClass* cls = state->classesByPointer[nativeClass];
379+
// NSLog(@"find init for class: %@, cls: %p", nativeClass, cls);
380+
ObjCClassMember* newMethod = findInitializerForArgs(env, &cls->members, argc, argv);
381+
// NSLog(@"new init: %p", newMethod);
382+
if (newMethod != nullptr) {
383+
method = newMethod;
384+
}
385+
}
386+
227387
Cif* cif = method->cif;
228388
if (cif == nullptr) {
229389
cif = method->cif =
230390
method->bridgeState->getMethodCif(env, method->methodOrGetter.signatureOffset);
231391
}
232392

233-
size_t argc = cif->argc;
393+
argc = cif->argc;
234394
napi_get_cb_info(env, cbinfo, &argc, cif->argv, &jsThis, nullptr);
235395

236396
void* avalues[cif->cif.nargs];

NativeScript/ffi/Object.mm

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,16 +168,16 @@ void finalize_objc_object(napi_env /*env*/, void* data, void* hint) {
168168
if (isClass) {
169169
result = constructor;
170170
} else {
171-
NAPI_GUARD(napi_new_instance(env, constructor, 0, nullptr, &result)) {
172-
NAPI_THROW_LAST_ERROR
173-
return nullptr;
174-
}
171+
napi_value ext;
172+
napi_create_external(env, nullptr, nullptr, nullptr, &ext);
175173

176-
NAPI_GUARD(napi_wrap(env, result, obj, nullptr, nullptr, nullptr)) {
174+
NAPI_GUARD(napi_new_instance(env, constructor, 1, &ext, &result)) {
177175
NAPI_THROW_LAST_ERROR
178176
return nullptr;
179177
}
180178

179+
napi_wrap(env, result, obj, nullptr, nullptr, nullptr);
180+
181181
if (ownership == kUnownedObject) {
182182
[obj retain];
183183
}

0 commit comments

Comments
 (0)