break out method generation into an _eval_closure method
[gitmo/Class-MOP.git] / MOP.xs
diff --git a/MOP.xs b/MOP.xs
index cc532e1..09e90cd 100644 (file)
--- a/MOP.xs
+++ b/MOP.xs
@@ -1,3 +1,9 @@
+/* There's a lot of cases of doubled parens in here like this:
+
+  while ( (he = ...) ) {
+
+This shuts up warnings from gcc -Wall
+*/
 
 #include "EXTERN.h"
 #include "perl.h"
@@ -19,6 +25,149 @@ U32 hash_package_name;
 SV *key_body;
 U32 hash_body;
 
+SV* method_metaclass;
+SV* associated_metaclass;
+SV* wrap;
+
+
+#define check_package_cache_flag(stash) mop_check_package_cache_flag(aTHX_ stash)
+#ifdef HvMROMETA /* 5.10.0 */
+
+#ifndef mro_meta_init
+#define mro_meta_init(stash) Perl_mro_meta_init(aTHX_ stash) /* used in HvMROMETA macro */
+#endif /* !mro_meta_init */
+
+static UV
+mop_check_package_cache_flag(pTHX_ HV* stash) {
+    assert(SvTYPE(stash) == SVt_PVHV);
+
+    return HvMROMETA(stash)->pkg_gen; /* mro::get_pkg_gen($pkg) */
+}
+
+#else /* pre 5.10.0 */
+
+static UV
+mop_check_package_cache_flag(pTHX_ HV* stash) {
+    PERL_UNUSED_ARG(stash);
+    assert(SvTYPE(stash) == SVt_PVHV);
+
+    return PL_sub_generation;
+}
+#endif
+
+#define call0(s, m)  mop_call0(aTHX_ s, m)
+static SV*
+mop_call0(pTHX_ SV* const self, SV* const method) {
+    dSP;
+    SV* ret;
+
+    PUSHMARK(SP);
+    XPUSHs(self);
+    PUTBACK;
+
+    call_sv(method, G_SCALAR | G_METHOD);
+
+    SPAGAIN;
+    ret = POPs;
+    PUTBACK;
+
+    return ret;
+}
+
+static void
+mop_update_method_map(pTHX_ SV* const self, SV* const class_name, HV* const stash, HV* const map) {
+    const char* const class_name_pv = HvNAME(stash); /* must be HvNAME(stash), not SvPV_nolen_const(class_name) */
+    SV*   method_metaclass_name;
+    char* method_name;
+    I32   method_name_len;
+    GV* gv;
+    dSP;
+
+    /* this function massivly overlaps with the xs version of
+     * get_all_package_symbols. a common c function to walk the symbol table
+     * should be factored out and used by both.  --rafl */
+
+    hv_iterinit(stash);
+    while ( (gv = (GV*)hv_iternextsv(stash, &method_name, &method_name_len)) ) {
+        CV* cv;
+        switch (SvTYPE (gv)) {
+#ifndef SVt_RV
+            case SVt_RV:
+#endif
+            case SVt_IV:
+            case SVt_PV:
+                /* rafl says that this wastes memory savings that GvSVs have
+                   in 5.8.9 and 5.10.x. But without it some tests fail. rafl
+                   says the right thing to do is to handle GvSVs differently
+                   here. */
+                gv_init((GV*)gv, stash, method_name, method_name_len, GV_ADDMULTI);
+                /* fall through */
+            default:
+                break;
+        }
+
+        if ( SvTYPE(gv) == SVt_PVGV && (cv = GvCVu(gv)) ) {
+            GV* const cvgv = CvGV(cv);
+            /* ($cvpkg_name, $cv_name) = get_code_info($cv) */
+            const char* const cvpkg_name = HvNAME(GvSTASH(cvgv));
+            const char* const cv_name    = GvNAME(cvgv);
+            SV* method_slot;
+            SV* method_object;
+
+            /* this checks to see that the subroutine is actually from our package  */
+            if ( !(strEQ(cvpkg_name, "constant") && strEQ(cv_name, "__ANON__")) ) {
+                if ( strNE(cvpkg_name, class_name_pv) ) {
+                    continue;
+                }
+            }
+
+            method_slot = *hv_fetch(map, method_name, method_name_len, TRUE);
+            if ( SvOK(method_slot) ) {
+                SV* const body = call0(method_slot, key_body); /* $method_object->body() */
+                if ( SvROK(body) && ((CV*) SvRV(body)) == cv ) {
+                    continue;
+                }
+            }
+
+            method_metaclass_name = call0(self, method_metaclass); /* $self->method_metaclass() */
+
+            /*
+                $method_object = $method_metaclass->wrap(
+                    $cv,
+                    associated_metaclass => $self,
+                    package_name         => $class_name,
+                    name                 => $method_name
+                );
+            */
+            ENTER;
+            SAVETMPS;
+
+            PUSHMARK(SP);
+            EXTEND(SP, 8);
+            PUSHs(method_metaclass_name); /* invocant */
+            mPUSHs(newRV_inc((SV*)cv));
+            PUSHs(associated_metaclass);
+            PUSHs(self);
+            PUSHs(key_package_name);
+            PUSHs(class_name);
+            PUSHs(key_name);
+            mPUSHs(newSVpv(method_name, method_name_len));
+            PUTBACK;
+
+            call_sv(wrap, G_SCALAR | G_METHOD);
+            SPAGAIN;
+            method_object = POPs;
+            PUTBACK;
+            /* $map->{$method_name} = $method_object */
+            sv_setsv(method_slot, method_object);
+
+            FREETMPS;
+            LEAVE;
+        }
+    }
+}
+
+
 /*
 get_code_info:
   Pass in a coderef, returns:
@@ -39,6 +188,10 @@ BOOT:
     PERL_HASH(hash_package, "package", 7);
     PERL_HASH(hash_package_name, "package_name", 12);
 
+    method_metaclass     = newSVpvs("method_metaclass");
+    wrap                 = newSVpvs("wrap");
+    associated_metaclass = newSVpvs("associated_metaclass");
+
 
 PROTOTYPES: ENABLE
 
@@ -50,14 +203,14 @@ get_code_info(coderef)
     char* name;
     char* pkg;
   PPCODE:
-    if( SvOK(coderef) && SvROK(coderef) && SvTYPE(SvRV(coderef)) == SVt_PVCV){
+    if ( SvOK(coderef) && SvROK(coderef) && SvTYPE(SvRV(coderef)) == SVt_PVCV ) {
       coderef = SvRV(coderef);
       /* I think this only gets triggered with a mangled coderef, but if
          we hit it without the guard, we segfault. The slightly odd return
          value strikes me as an improvement (mst)
       */
 #ifdef isGV_with_GP
-      if ( isGV_with_GP(CvGV(coderef))) {
+      if ( isGV_with_GP(CvGV(coderef)) ) {
 #endif
         pkg     = HvNAME( GvSTASH(CvGV(coderef)) );
         name    = GvNAME( CvGV(coderef) );
@@ -85,11 +238,11 @@ get_all_package_symbols(self, ...)
         SV *type_filter = NULL;
         register HE *he;
     PPCODE:
-        if (! SvROK(self)) {
+        if ( ! SvROK(self) ) {
             die("Cannot call get_all_package_symbols as a class method");
         }
 
-        switch ( GIMME_V ) {
+        switch (GIMME_V) {
             case G_VOID: return; break;
             case G_SCALAR: ST(0) = &PL_sv_undef; return; break;
         }
@@ -98,25 +251,48 @@ get_all_package_symbols(self, ...)
 
         PUTBACK;
 
-        if ((he = hv_fetch_ent((HV *)SvRV(self), key_package, 0, hash_package)))
+        if ( (he = hv_fetch_ent((HV *)SvRV(self), key_package, 0, hash_package)) )
             stash = gv_stashsv(HeVAL(he),0);
 
-        if ( stash ) {
+        if (stash) {
 
             (void)hv_iterinit(stash);
 
             if ( type_filter && SvPOK(type_filter) ) {
                 const char *const type = SvPV_nolen(type_filter);
 
-                while ((he = hv_iternext(stash))) {
+                while ( (he = hv_iternext(stash)) ) {
                     SV *const gv = HeVAL(he);
-                    SV *sv;
+                    SV *sv = NULL;
                     char *key;
                     STRLEN keylen;
                     char *package;
                     SV *fq;
 
                     switch( SvTYPE(gv) ) {
+#ifndef SVt_RV
+                        case SVt_RV:
+#endif
+                        case SVt_PV:
+                        case SVt_IV:
+                            /* expand the gv into a real typeglob if it
+                             * contains stub functions and we were asked to
+                             * return CODE symbols */
+                            if (*type == 'C') {
+                                if (SvROK(gv)) {
+                                    /* we don't really care about the length,
+                                       but that's the API */
+                                    key = HePV(he, keylen);
+                                    package = HvNAME(stash);
+                                    fq = newSVpvf("%s::%s", package, key);
+                                    sv = (SV*)get_cv(SvPV_nolen(fq), 0);
+                                    break;
+                                }
+
+                                key = HePV(he, keylen);
+                                gv_init((GV *)gv, stash, key, keylen, GV_ADDMULTI);
+                            }
+                            /* fall through */
                         case SVt_PVGV:
                             switch (*type) {
                                 case 'C': sv = (SV *)GvCVu(gv); break; /* CODE */
@@ -128,25 +304,11 @@ get_all_package_symbols(self, ...)
                                           croak("Unknown type %s\n", type);
                             }
                             break;
-                        case SVt_RV:
-                            /* BAH! constants are horrible */
-
-                            if (!SvROK (gv)) {
-                                continue;
-                            }
-
-                            /* we don't really care about the length,
-                               but that's the API */
-                            key = HePV(he, keylen);
-                            package = HvNAME(stash);
-                            fq = newSVpvf("%s::%s", package, key);
-                            sv = (SV*)get_cv(SvPV_nolen(fq), 0);
-                            break;
                         default:
                             continue;
                     }
 
-                    if ( sv ) {
+                    if (sv) {
                         SV *key = hv_iterkeysv(he);
                         SPAGAIN;
                         EXTEND(SP, 2);
@@ -158,7 +320,7 @@ get_all_package_symbols(self, ...)
             } else {
                 EXTEND(SP, HvKEYS(stash) * 2);
 
-                while ((he = hv_iternext(stash))) {
+                while ( (he = hv_iternext(stash)) ) {
                     SV *key = hv_iterkeysv(he);
                     SV *sv = HeVAL(he);
                     SPAGAIN;
@@ -176,11 +338,11 @@ name(self)
     PREINIT:
         register HE *he;
     PPCODE:
-        if (! SvROK(self)) {
+        if ( ! SvROK(self) ) {
             die("Cannot call name as a class method");
         }
 
-        if ((he = hv_fetch_ent((HV *)SvRV(self), key_package, 0, hash_package)))
+        if ( (he = hv_fetch_ent((HV *)SvRV(self), key_package, 0, hash_package)) )
             XPUSHs(HeVAL(he));
         else
             ST(0) = &PL_sv_undef;
@@ -193,11 +355,11 @@ name(self)
     PREINIT:
         register HE *he;
     PPCODE:
-        if (! SvROK(self)) {
+        if ( ! SvROK(self) ) {
             die("Cannot call name as a class method");
         }
 
-        if ((he = hv_fetch_ent((HV *)SvRV(self), key_name, 0, hash_name)))
+        if ( (he = hv_fetch_ent((HV *)SvRV(self), key_name, 0, hash_name)) )
             XPUSHs(HeVAL(he));
         else
             ST(0) = &PL_sv_undef;
@@ -210,11 +372,11 @@ name(self)
     PREINIT:
         register HE *he;
     PPCODE:
-        if (! SvROK(self)) {
+        if ( ! SvROK(self) ) {
             die("Cannot call name as a class method");
         }
 
-        if ((he = hv_fetch_ent((HV *)SvRV(self), key_name, 0, hash_name)))
+        if ( (he = hv_fetch_ent((HV *)SvRV(self), key_name, 0, hash_name)) )
             XPUSHs(HeVAL(he));
         else
             ST(0) = &PL_sv_undef;
@@ -225,11 +387,11 @@ package_name(self)
     PREINIT:
         register HE *he;
     PPCODE:
-        if (! SvROK(self)) {
+        if ( ! SvROK(self) ) {
             die("Cannot call package_name as a class method");
         }
 
-        if ((he = hv_fetch_ent((HV *)SvRV(self), key_package_name, 0, hash_package_name)))
+        if ( (he = hv_fetch_ent((HV *)SvRV(self), key_package_name, 0, hash_package_name)) )
             XPUSHs(HeVAL(he));
         else
             ST(0) = &PL_sv_undef;
@@ -240,11 +402,49 @@ body(self)
     PREINIT:
         register HE *he;
     PPCODE:
-        if (! SvROK(self)) {
+        if ( ! SvROK(self) ) {
             die("Cannot call body as a class method");
         }
 
-        if ((he = hv_fetch_ent((HV *)SvRV(self), key_body, 0, hash_body)))
+        if ( (he = hv_fetch_ent((HV *)SvRV(self), key_body, 0, hash_body)) )
             XPUSHs(HeVAL(he));
         else
             ST(0) = &PL_sv_undef;
+
+
+MODULE = Class::MOP    PACKAGE = Class::MOP::Class
+
+void
+get_method_map(self)
+    SV* self
+    PREINIT:
+        SV* const class_name = HeVAL( hv_fetch_ent((HV*)SvRV(self), key_package, TRUE, hash_package) );
+        HV* const stash      = gv_stashsv(class_name, TRUE);
+        UV  const current    = check_package_cache_flag(stash);
+        SV* const cache_flag = *hv_fetchs((HV*)SvRV(self), "_package_cache_flag", TRUE);
+        SV* const map_ref    = *hv_fetchs((HV*)SvRV(self), "methods", TRUE);
+    PPCODE:
+        if ( ! SvRV(self) ) {
+            die("Cannot call get_method_map as a class method");
+        }
+
+        /* in  $self->{methods} does not yet exist (or got deleted) */
+        if ( ! (SvROK(map_ref) && SvTYPE(SvRV(map_ref)) == SVt_PVHV) ) {
+            SV* new_map_ref = newRV_noinc((SV*)newHV());
+            sv_2mortal(new_map_ref);
+            sv_setsv(map_ref, new_map_ref);
+        }
+
+        if ( ! (SvOK(cache_flag) && SvUV(cache_flag) == current) ) {
+            ENTER;
+            SAVETMPS;
+
+            mop_update_method_map(aTHX_ self, class_name, stash, (HV*)SvRV(map_ref));
+            sv_setuv(cache_flag, check_package_cache_flag(stash)); /* update_cache_flag() */
+
+            FREETMPS;
+            LEAVE;
+        }
+
+        XPUSHs(map_ref);
+