Browse files

Check if a class, or an instance of that class, conforms to a protocol.

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...
1 parent b62b438 commit 40d2e04a5a65c45cbd7e8225136a6fce707c6edc @alloy alloy committed Nov 22, 2010
Showing with 179 additions and 0 deletions.
  1. +91 −0 object.c
  2. +19 −0 spec/macruby/core/object_spec.rb
  3. +41 −0 spec/macruby/fixtures/object.m
  4. +28 −0 spec/macruby/fixtures/object.rb
View
91 object.c
@@ -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
*
@@ -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);
View
19 spec/macruby/core/object_spec.rb
@@ -3,6 +3,7 @@
FixtureCompiler.require! "object"
TestObject # force dynamic load
+TestProtocolConformance
require File.join(FIXTURES, 'object')
describe "A pure MacRuby Class" do
@@ -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
View
41 spec/macruby/fixtures/object.m
@@ -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) {}
View
28 spec/macruby/fixtures/object.rb
@@ -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.