Add duck_type to Mouse::Util::TypeConstraints
gfx [Wed, 9 Dec 2009 11:54:54 +0000 (20:54 +0900)]
lib/Mouse/PurePerl.pm
lib/Mouse/Util/TypeConstraints.pm
mouse.h
xs-src/MouseTypeConstraints.xs
xs-src/MouseUtil.xs

index 1f77bf0..fc6b457 100644 (file)
@@ -97,6 +97,32 @@ sub generate_isa_predicate_for {
     return $predicate;
 }
 
+sub generate_can_predicate_for {
+    my($methods_ref, $name) = @_;
+
+    my @methods = @{$methods_ref};
+
+    my $predicate = sub{
+        my($instance) = @_;
+        if(Scalar::Util::blessed($instance)){
+            foreach my $method(@methods){
+                if(!$instance->can($method)){
+                    return 0;
+                }
+            }
+            return 1;
+        }
+        return 0;
+    };
+
+    if(defined $name){
+        no strict 'refs';
+        *{ caller() . '::' . $name } = $predicate;
+        return;
+    }
+
+    return $predicate;
+}
 
 package
     Mouse::Util::TypeConstraints;
index 2b2d558..fc99aa8 100644 (file)
@@ -11,7 +11,11 @@ Mouse::Exporter->setup_import_methods(
     as_is => [qw(
         as where message optimize_as
         from via
-        type subtype coerce class_type role_type enum
+
+        type subtype class_type role_type duck_type
+        enum
+        coerce
+
         find_type_constraint
     )],
 );
@@ -172,6 +176,22 @@ sub role_type {
     );
 }
 
+sub duck_type {
+    my($name, @methods);
+
+    if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){
+        $name = shift;
+    }
+
+    @methods = (@_ == 1 && ref($_[0]) eq 'ARRAY') ? @{$_[0]} : @_;
+
+    return _create_type 'type', $name => (
+        optimized_as => Mouse::Util::generate_can_predicate_for(\@methods),
+
+        type => 'DuckType',
+    );
+}
+
 sub typecast_constraints { # DEPRECATED
     my($class, $pkg, $type, $value) = @_;
     Carp::croak("wrong arguments count") unless @_ == 4;
diff --git a/mouse.h b/mouse.h
index efed6ca..f30029f 100644 (file)
--- a/mouse.h
+++ b/mouse.h
@@ -201,6 +201,7 @@ int mouse_tc_FileHandle(pTHX_ SV*, SV* const sv);
 int mouse_tc_Object    (pTHX_ SV*, SV* const sv);
 
 CV* mouse_generate_isa_predicate_for(pTHX_ SV* const klass, const char* const predicate_name);
+CV* mouse_generate_can_predicate_for(pTHX_ SV* const klass, const char* const predicate_name);
 
 int mouse_is_an_instance_of(pTHX_ HV* const stash, SV* const instance);
 
index 5260d88..96cb6eb 100644 (file)
@@ -320,6 +320,7 @@ mouse_types_check(pTHX_ AV* const types, SV* const sv) {
 #define MY_CXT_KEY "Mouse::Util::TypeConstraints::_guts" XS_VERSION
 typedef struct sui_cxt{
     GV* universal_isa;
+    GV* universal_can;
 } my_cxt_t;
 START_MY_CXT
 
@@ -411,6 +412,56 @@ mouse_is_an_instance_of_universal(pTHX_ SV* const data, SV* const sv){
     return SvROK(sv) && SvOBJECT(SvRV(sv));
 }
 
+static int
+mouse_can_methods(pTHX_ AV* const methods, SV* const instance){
+    if(IsObject(instance)){
+        dMY_CXT;
+        HV* const mystash      = SvSTASH(SvRV(instance));
+        GV* const mycan        = gv_fetchmeth_autoload(mystash, "can", sizeof("can")-1, 0);
+        bool const use_builtin = (mycan == NULL || GvCV(mycan) == GvCV(MY_CXT.universal_isa)) ? TRUE : FALSE;
+        I32 const len           = AvFILLp(methods) + 1;
+        I32 i;
+        for(i = 0; i < len; i++){
+            SV* const name = MOUSE_av_at(methods, i);
+
+            if(use_builtin){
+                if(!gv_fetchmeth_autoload(mystash, SvPVX(name), SvCUR(name), 0)){
+                    return FALSE;
+                }
+            }
+            else{
+                bool ok;
+                dSP;
+
+                ENTER;
+                SAVETMPS;
+
+                PUSHMARK(SP);
+                EXTEND(SP, 2);
+                PUSHs(instance);
+                PUSHs(sv_mortalcopy(name));
+                PUTBACK;
+
+                call_method("can", G_SCALAR);
+
+                SPAGAIN;
+                ok = SvTRUE(TOPs);
+                (void)POPs;
+                PUTBACK;
+
+                FREETMPS;
+                LEAVE;
+
+                if(!ok){
+                    return FALSE;
+                }
+            }
+        }
+        return TRUE;
+    }
+    return FALSE;
+}
+
 static MGVTBL mouse_util_type_constraints_vtbl; /* not used, only for identity */
 
 static CV*
@@ -456,6 +507,32 @@ mouse_generate_isa_predicate_for(pTHX_ SV* const klass, const char* const predic
     return mouse_tc_generate(aTHX_ predicate_name, fptr, param);
 }
 
+CV*
+mouse_generate_can_predicate_for(pTHX_ SV* const methods, const char* const predicate_name){
+    AV* av;
+    AV* const param = newAV_mortal();
+    I32 len;
+    I32 i;
+
+    SvGETMAGIC(methods);
+    if(!IsArrayRef(methods)){
+        croak("You must pass an ARRAY ref method names");
+    }
+    av = (AV*)SvRV(methods);
+
+    len = av_len(av) + 1;
+    for(i = 0; i < len; i++){
+        SV* const name = *av_fetch(av, i, TRUE);
+        STRLEN pvlen;
+        const char* const pv = SvPV_const(name, pvlen);
+
+        av_push(param, newSVpvn_share(pv, pvlen, 0U));
+    }
+
+    return mouse_tc_generate(aTHX_ predicate_name, (check_fptr_t)mouse_can_methods, (SV*)param);
+}
+
+
 XS(XS_Mouse_constraint_check) {
     dVAR;
     dXSARGS;
@@ -474,6 +551,9 @@ static void
 setup_my_cxt(pTHX_ pMY_CXT){
     MY_CXT.universal_isa = gv_fetchpvs("UNIVERSAL::isa", GV_ADD, SVt_PVCV);
     SvREFCNT_inc_simple_void_NN(MY_CXT.universal_isa);
+
+    MY_CXT.universal_can = gv_fetchpvs("UNIVERSAL::can", GV_ADD, SVt_PVCV);
+    SvREFCNT_inc_simple_void_NN(MY_CXT.universal_can);
 }
 
 #define DEFINE_TC(name) mouse_tc_generate(aTHX_ "Mouse::Util::TypeConstraints::" STRINGIFY(name), CAT2(mouse_tc_, name), NULL)
index 3e97648..93160fc 100644 (file)
@@ -347,27 +347,35 @@ OUTPUT:
     RETVAL
 
 void
-generate_isa_predicate_for(SV* klass, SV* predicate_name = NULL)
+generate_isa_predicate_for(SV* arg, SV* predicate_name = NULL)
+ALIAS:
+    generate_isa_predicate_for = 0
+    generate_can_predicate_for = 1
 PPCODE:
 {
     const char* name_pv = NULL;
     CV* xsub;
 
-    SvGETMAGIC(klass);
+    SvGETMAGIC(arg);
 
-    if(!SvOK(klass)){
-        croak("You must define a class name");
+    if(!SvOK(arg)){
+        croak("You must define %s", ix == 0 ? "a class name" : "method names");
     }
 
     if(predicate_name){
         SvGETMAGIC(predicate_name);
         if(!SvOK(predicate_name)){
-            croak("You must define a predicate_name");
+            croak("You must define %s", "a predicate name");
         }
         name_pv = SvPV_nolen_const(predicate_name);
     }
 
-    xsub = mouse_generate_isa_predicate_for(aTHX_ klass, name_pv);
+    if(ix == 0){
+        xsub = mouse_generate_isa_predicate_for(aTHX_ arg, name_pv);
+    }
+    else{
+        xsub = mouse_generate_can_predicate_for(aTHX_ arg, name_pv);
+    }
 
     if(predicate_name == NULL){ /* anonymous predicate */
         XPUSHs( newRV_noinc((SV*)xsub) );