Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions NativeScript/runtime/MetadataBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ class MetadataBuilder {
v8::Local<v8::Object> parent);

private:
// The binding of an object whose static type adds protocols to `meta`: a
// template inheriting from the class's own binding with the protocols'
// extra members, cached per class and protocol set. Never published as the
// class's constructor.
static v8::Local<v8::FunctionTemplate> GetOrCreateProtocolBindingTemplate(
v8::Local<v8::Context> context, const BaseClassMeta* meta,
KnownUnknownClassPair pair,
const std::vector<std::string>& additionalProtocols);
static v8::Local<v8::FunctionTemplate>
GetOrCreateConstructorFunctionTemplateInternal(
v8::Local<v8::Context> context, const BaseClassMeta* meta,
Expand Down
154 changes: 145 additions & 9 deletions NativeScript/runtime/MetadataBuilder.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "MetadataBuilder.h"
#include <Foundation/Foundation.h>
#include <algorithm>
#include "ArgConverter.h"
#include "Caches.h"
#include "Constants.h"
Expand Down Expand Up @@ -532,12 +533,15 @@ throw NativeScriptException(
Local<FunctionTemplate> ctorFuncTemplate;
auto cache = Caches::Get(isolate);

if (additionalProtocols.empty()) {
auto it = cache->CtorFuncTemplates.find(meta);
if (it != cache->CtorFuncTemplates.end()) {
ctorFuncTemplate = it->second->Get(isolate);
return ctorFuncTemplate;
}
if (!additionalProtocols.empty()) {
return MetadataBuilder::GetOrCreateProtocolBindingTemplate(context, meta, pair,
additionalProtocols);
}

auto cached = cache->CtorFuncTemplates.find(meta);
if (cached != cache->CtorFuncTemplates.end()) {
ctorFuncTemplate = cached->second->Get(isolate);
return ctorFuncTemplate;
}

std::string className;
Expand Down Expand Up @@ -589,9 +593,6 @@ throw NativeScriptException(
MetadataBuilder::RegisterInstanceMethods(context, ctorFuncTemplate, meta, pair, instanceMembers);
MetadataBuilder::RegisterInstanceProtocols(context, ctorFuncTemplate, meta, meta->name(), pair,
instanceMembers);
MetadataBuilder::RegisterAdditionalProtocols(context, ctorFuncTemplate, pair, additionalProtocols,
instanceMembers);

ctorFuncTemplate->PrototypeTemplate()->Set(
tns::ToV8String(isolate, "toString"),
FunctionTemplate::New(isolate, MetadataBuilder::ToStringFunctionCallback));
Expand Down Expand Up @@ -662,6 +663,141 @@ NamedPropertyHandlerConfiguration config(nullptr, MetadataBuilder::SwizzledInsta
return ctorFuncTemplate;
}

namespace {

// Keyed by the object's runtime class: optional protocol members are bound
// only when that class implements them.
struct ProtocolBindings {
struct Binding {
const BaseClassMeta* meta;
std::vector<std::string> protocols;
Global<FunctionTemplate> bindingTemplate;
};
robin_hood::unordered_map<Class, std::vector<Binding>> byClass;
};

// The instance members the bindings of `meta`'s class chain are guaranteed to
// have, with the property accessor counts RegisterInstanceProperties compares
// against: a protocol property replaces the class's only where it adds an
// accessor, and a protocol method never replaces one the class chain has.
// Each level is checked against its own class, the one class that every class
// whose wrapping could have built that level's binding descends from.
void CollectInstanceMemberNames(const BaseClassMeta* meta, Class klass, bool ownMembers,
robin_hood::unordered_map<std::string, uint8_t>& names) {
KnownUnknownClassPair pair(klass);
if (ownMembers) {
for (auto it = meta->instanceMethods->begin(); it != meta->instanceMethods->end(); it++) {
const MethodMeta* methodMeta = (*it).valuePtr();
if (methodMeta->isAvailableInClasses(pair, false)) {
names.emplace(methodMeta->name(), 0);
}
}
for (auto it = meta->instanceProps->begin(); it != meta->instanceProps->end(); it++) {
const PropertyMeta* propMeta = (*it).valuePtr();
if (!propMeta->isAvailableInClasses(pair, false)) {
continue;
}
uint8_t accessors = (propMeta->hasGetter() ? 1 : 0) + (propMeta->hasSetter() ? 1 : 0);
if (accessors == 0) {
continue;
}
uint8_t& seen = names[propMeta->name()];
seen = std::max(seen, accessors);
}
for (auto it = meta->protocols->begin(); it != meta->protocols->end(); it++) {
const Meta* protocolMeta = ArgConverter::GetMeta((*it).valuePtr());
if (protocolMeta != nullptr && protocolMeta->type() == MetaType::ProtocolType) {
CollectInstanceMemberNames(static_cast<const BaseClassMeta*>(protocolMeta), klass, true,
names);
}
}
}
if (meta->type() != MetaType::Interface) {
return;
}
const char* baseName = static_cast<const InterfaceMeta*>(meta)->baseName();
if (baseName == nullptr) {
return;
}
const Meta* baseMeta = ArgConverter::GetMeta(baseName);
if (baseMeta != nullptr && baseMeta->type() == MetaType::Interface) {
// A nil class would count every member as implemented.
Class baseClass = objc_getClass(baseMeta->name());
CollectInstanceMemberNames(static_cast<const BaseClassMeta*>(baseMeta),
baseClass != nil ? baseClass : klass, baseMeta->isAvailable(),
names);
}
}

} // namespace

Local<FunctionTemplate> MetadataBuilder::GetOrCreateProtocolBindingTemplate(
Local<Context> context, const BaseClassMeta* meta, KnownUnknownClassPair pair,
const std::vector<std::string>& additionalProtocols) {
Isolate* isolate = v8::Isolate::GetCurrent();
Class runtimeClass = pair.realClass();

ProtocolBindings* bindings = Caches::StateFor<ProtocolBindings>(isolate);
auto findBinding = [&]() -> Local<FunctionTemplate> {
if (bindings != nullptr) {
auto it = bindings->byClass.find(runtimeClass);
if (it != bindings->byClass.end()) {
for (const ProtocolBindings::Binding& binding : it->second) {
if (binding.meta == meta && binding.protocols == additionalProtocols) {
return binding.bindingTemplate.Get(isolate);
}
}
}
}
return Local<FunctionTemplate>();
};
Local<FunctionTemplate> cached = findBinding();
if (!cached.IsEmpty()) {
return cached;
}

Local<FunctionTemplate> classTemplate =
MetadataBuilder::GetOrCreateConstructorFunctionTemplate(context, meta, pair);
// Seeded from the metadata's classes, never the runtime class: a member the
// class chain's bindings may lack is left to the protocols to bind.
robin_hood::unordered_map<std::string, uint8_t> names;
CollectInstanceMemberNames(meta, pair.known, true, names);

Local<FunctionTemplate> bindingTemplate = FunctionTemplate::New(isolate);
bindingTemplate->InstanceTemplate()->SetInternalFieldCount(1);
bindingTemplate->SetClassName(tns::ToV8String(isolate, meta->name()));
bindingTemplate->Inherit(classTemplate);
MetadataBuilder::RegisterAdditionalProtocols(context, bindingTemplate, pair, additionalProtocols,
names);

Local<v8::Function> bindingFunc;
Local<v8::Function> classFunc;
Local<Value> prototype;
bool success = bindingTemplate->GetFunction(context).ToLocal(&bindingFunc) &&
classTemplate->GetFunction(context).ToLocal(&classFunc) &&
bindingFunc->Get(context, tns::ToV8String(isolate, "prototype"))
.ToLocal(&prototype) &&
prototype->IsObject();
tns::Assert(success, isolate);
// Instances report the class's constructor, which is the one published
// globally and the one subclasses extend.
prototype.As<Object>()
->DefineOwnProperty(context, tns::ToV8String(isolate, "constructor"), classFunc,
PropertyAttribute::DontEnum)
.FromMaybe(false);

// Building the class's binding can wrap objects of this class first.
cached = findBinding();
if (!cached.IsEmpty()) {
return cached;
}
if (bindings != nullptr) {
bindings->byClass[runtimeClass].push_back(ProtocolBindings::Binding{
meta, additionalProtocols, Global<FunctionTemplate>(isolate, bindingTemplate)});
}
return bindingTemplate;
}

void MetadataBuilder::ToStringFunctionCallback(const FunctionCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
Local<Object> thiz = info.This();
Expand Down
42 changes: 42 additions & 0 deletions TestFixtures/Api/TNSPseudoDataTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,44 @@

@end

@protocol TNSOptionalMemberProtocol

@optional
-(NSString*)optionalMember;

@end

@protocol TNSReadonlyValueProtocol

@property (nonatomic, readonly) NSInteger value;

@end

@interface TNSReadwriteValue : NSObject

@property (nonatomic) NSInteger value;

@end

@protocol TNSDeclaredMemberProtocol

-(NSString*)declaredMember;

@end

@interface TNSDeclaredMemberBase : NSObject
@end

// Declared for the base class, implemented only by TNSDeclaredMemberSubclass.
@interface TNSDeclaredMemberBase (TNSDeclaredOnly)

-(NSString*)declaredMember;

@end

@interface TNSDeclaredMemberSubclass : TNSDeclaredMemberBase
@end

@interface TNSType : NSObject

-(void)method;
Expand All @@ -24,5 +62,9 @@

+(id<Proto1, Proto2>)getId;
+(TNSType<Proto1, Proto2>*)getType;
+(id<TNSOptionalMemberProtocol>)getWithOptionalMember;
+(id<TNSOptionalMemberProtocol>)getWithoutOptionalMember;
+(id<TNSReadonlyValueProtocol>)getReadwriteValue;
+(id<TNSDeclaredMemberProtocol>)getDeclaredMemberSubclass;

@end
47 changes: 47 additions & 0 deletions TestFixtures/Api/TNSPseudoDataTypes.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,37 @@ -(void)methodFromProto2:(NSString*)param;

@end

@interface TNSWithOptionalMember : NSObject <TNSOptionalMemberProtocol>
@end

@implementation TNSWithOptionalMember

-(NSString*)optionalMember {
return @"optional member";
}

@end

@interface TNSWithoutOptionalMember : NSObject <TNSOptionalMemberProtocol>
@end

@implementation TNSWithoutOptionalMember
@end

@implementation TNSReadwriteValue
@end

@implementation TNSDeclaredMemberBase
@end

@implementation TNSDeclaredMemberSubclass

-(NSString*)declaredMember {
return @"declared member";
}

@end

@implementation TNSType

-(void)method {
Expand Down Expand Up @@ -41,6 +72,22 @@ @implementation TNSPseudoDataType
return internal;
}

+(id<TNSOptionalMemberProtocol>)getWithOptionalMember {
return [[TNSWithOptionalMember alloc] init];
}

+(id<TNSOptionalMemberProtocol>)getWithoutOptionalMember {
return [[TNSWithoutOptionalMember alloc] init];
}

+(id<TNSReadonlyValueProtocol>)getReadwriteValue {
return [[TNSReadwriteValue alloc] init];
}

+(id<TNSDeclaredMemberProtocol>)getDeclaredMemberSubclass {
return [[TNSDeclaredMemberSubclass alloc] init];
}

+(TNSType<Proto1, Proto2>*)getType {
TNSPseudoDataTypeInternal* internal = [[TNSPseudoDataTypeInternal alloc] init];
internal.propertyFromProto1 = @"property from proto1";
Expand Down
Loading
Loading