Skip to content

Commit

Permalink
Check if a class, or an instance of that class, conforms to a protocol.
Browse files Browse the repository at this point in the history
The conformsToProtocol: implementation checks if the class implements:
* the required class methods
* the required instance methods
* the methods required by protocols adopted by the protocol

git-svn-id: http://svn.macosforge.org/repository/ruby/MacRuby/trunk@4930 23306eb0-4c56-4727-a40e-e92c0eb68959
  • Loading branch information
alloy committed Nov 22, 2010
1 parent b62b438 commit 40d2e04
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 0 deletions.
91 changes: 91 additions & 0 deletions object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2853,6 +2853,91 @@ rb_class_is_meta(VALUE klass, SEL sel)
return RCLASS_META(klass) ? Qtrue : Qfalse;
}

static bool
implementsProtocolMethods(Protocol *p, Class klass, bool instanceMethods)
{
unsigned int count = 0;
struct objc_method_description *list =
protocol_copyMethodDescriptionList(p, true, instanceMethods,
&count);
if (list != NULL) {
bool success = true;
for (unsigned int i = 0; i < count; i++) {
SEL msel = list[i].name;
Method m;
if (instanceMethods) {
m = class_getInstanceMethod(klass, msel);
}
else {
m = class_getClassMethod(klass, msel);
}
if (m == NULL) {
success = false;
break;
}
}
free(list);
if (success) {
return true;
}
}
return false;
}

static bool
conformsToProtocol(Class klass, Protocol *p)
{
if (implementsProtocolMethods(p, klass, true)
&& implementsProtocolMethods(p, klass, false)) {
unsigned int count = 0;
Protocol **list = protocol_copyProtocolList(p, &count);
bool success = true;
for (unsigned int i = 0; i < count; i++) {
if (!conformsToProtocol(klass, list[i])) {
success = false;
break;
}
}
free(list);
return success;
}
return false;
}

static bool
conformsToProtocolAndAncestors(void *self, SEL sel, Class klass,
void *protocol, IMP super)
{
if (protocol != NULL) {
Protocol *p = (Protocol *)protocol;
if (conformsToProtocol(klass, p)) {
return true;
}
}
if (super != NULL) {
return ((bool(*)(void *, SEL, Protocol *))super)(self, sel, protocol);
}
return false;
}

static IMP old_conformsToProtocol_imp = NULL;

static bool
robj_conformsToProtocol(void *self, SEL sel, void *protocol)
{
return conformsToProtocolAndAncestors(self, sel, object_getClass(self),
protocol, old_conformsToProtocol_imp);
}

static IMP old_conformsToProtocol_mimp = NULL;

static bool
robj_conformsToProtocol_m(void *self, SEL sel, void *protocol)
{
return conformsToProtocolAndAncestors(self, sel, (Class)self, protocol,
old_conformsToProtocol_mimp);
}

/*
* Document-class: Class
*
Expand Down Expand Up @@ -2951,6 +3036,12 @@ Init_Object(void)
RCLASS_SET_VERSION_FLAG(rb_cRubyObject, RCLASS_IS_OBJECT_SUBCLASS);
rb_define_object_special_methods(rb_cRubyObject);

SEL ctp = sel_registerName("conformsToProtocol:");
old_conformsToProtocol_imp = rb_objc_install_method((Class)rb_cRubyObject,
ctp, (IMP)robj_conformsToProtocol);
old_conformsToProtocol_mimp = rb_objc_install_method(
*(Class *)rb_cRubyObject, ctp, (IMP)robj_conformsToProtocol_m);

eqlSel = sel_registerName("eql?:");

rb_objc_define_method(*(VALUE *)rb_cModule, "alloc", rb_module_s_alloc, 0);
Expand Down
19 changes: 19 additions & 0 deletions spec/macruby/core/object_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

FixtureCompiler.require! "object"
TestObject # force dynamic load
TestProtocolConformance
require File.join(FIXTURES, 'object')

describe "A pure MacRuby Class" do
Expand All @@ -25,6 +26,24 @@
o.class.should == TestObjectPureMacRuby
o.initialized?.should == true
end

it "returns whether or not it conforms to the given protocol" do
o = TestObjectThatDoesNotCompletelyConformToProtocol
conforms = TestProtocolConformance.checkIfObjectConformsToTestProtocol(o)
conforms.should == 0

o = TestObjectThatDoesNotCompletelyConformToProtocol.new
conforms = TestProtocolConformance.checkIfObjectConformsToTestProtocol(o)
conforms.should == 0

o = TestObjectThatConformsToProtocol
conforms = TestProtocolConformance.checkIfObjectConformsToTestProtocol(o)
conforms.should == 1

o = TestObjectThatConformsToProtocol.new
conforms = TestProtocolConformance.checkIfObjectConformsToTestProtocol(o)
conforms.should == 1
end
end

# Atm we don't actually do anything with the given zone, so the current
Expand Down
41 changes: 41 additions & 0 deletions spec/macruby/fixtures/object.m
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,45 @@ + (BOOL)resolveClassMethod:(SEL)name

@end

@protocol AnotherTestProtocol

+ (int)anotherClassMethod;
- (int)anotherInstanceMethod;

@optional

+ (int)anotherOptionalClassMethod;
- (int)anotherOptionalInstanceMethod;

@end

@protocol TestProtocol <AnotherTestProtocol>

+ (int)aClassMethod;
+ (int)aClassMethodWithArg:(int)arg;
+ (int)aClassMethodWithArg:(int)arg1 anotherArg:(int)arg2;

- (int)anInstanceMethod;
- (int)anInstanceMethodWithArg:(int)arg;
- (int)anInstanceMethodWithArg:(int)arg1 anotherArg:(int)arg2;

@optional

+ (int)anOptionalClassMethod;
- (int)anOptionalInstanceMethod;

@end

@interface TestProtocolConformance : NSObject
@end

@implementation TestProtocolConformance

+ (BOOL)checkIfObjectConformsToTestProtocol:(id)object
{
return [object conformsToProtocol:@protocol(TestProtocol)];
}

@end

void Init_object(void) {}
28 changes: 28 additions & 0 deletions spec/macruby/fixtures/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,31 @@ def initialize
end
def initialized?; @initialized; end
end

class TestObjectThatDoesNotCompletelyConformToProtocol
def anInstanceMethod
end

def anInstanceMethodWithArg(arg)
end

def anInstanceMethodWithArg(arg1, anotherArg: arg2)
end

def self.aClassMethod
end

def self.aClassMethodWithArg(arg)
end

def self.aClassMethodWithArg(arg1, anotherArg: arg2)
end
end

class TestObjectThatConformsToProtocol < TestObjectThatDoesNotCompletelyConformToProtocol
def anotherInstanceMethod
end

def self.anotherClassMethod
end
end

0 comments on commit 40d2e04

Please sign in to comment.