summaryrefslogtreecommitdiff
path: root/ext/mro
diff options
context:
space:
mode:
authorNicholas Clark <nick@ccl4.org>2008-12-27 00:20:35 +0000
committerNicholas Clark <nick@ccl4.org>2008-12-27 21:12:12 +0000
commitb2685f0c86badfc357584d8dbfb2bf17057ea226 (patch)
tree69b0e3ace3542be24afd551380e2e0ecffc1500a /ext/mro
parentc60bad7b8870cf2745c93e1b99cbb504daa780b2 (diff)
downloadperl-b2685f0c86badfc357584d8dbfb2bf17057ea226.tar.gz
Proper pluggable Method Resolution Orders. 'c3' is now implemented outside the
core, in ext/mro/mro.xs. Also move mro::_nextcan() to mro.xs. It needs direct access to S_mro_get_linear_isa_c3(), and nothing on CPAN calls it, except via methods defined in mro.pm. Hence all users already require mro;
Diffstat (limited to 'ext/mro')
-rw-r--r--ext/mro/Changes6
-rw-r--r--ext/mro/Makefile.PL10
-rw-r--r--ext/mro/mro.pm382
-rw-r--r--ext/mro/mro.xs439
-rw-r--r--ext/mro/t/pluggable.t26
5 files changed, 863 insertions, 0 deletions
diff --git a/ext/mro/Changes b/ext/mro/Changes
new file mode 100644
index 0000000000..0dd224e18a
--- /dev/null
+++ b/ext/mro/Changes
@@ -0,0 +1,6 @@
+Revision history for Perl extension mro.
+
+1.01 Fri Dec 26 19:23:01 2008
+ - original version; created by h2xs 1.23 with options
+ -b 5.10.0 -c -A -n mro --skip-ppport
+ Migrate code from the core's mro.c
diff --git a/ext/mro/Makefile.PL b/ext/mro/Makefile.PL
new file mode 100644
index 0000000000..8ccd8873fb
--- /dev/null
+++ b/ext/mro/Makefile.PL
@@ -0,0 +1,10 @@
+use 5.010000;
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ NAME => 'mro',
+ VERSION_FROM => 'mro.pm', # finds $VERSION
+ ABSTRACT_FROM => 'mro.pm', # retrieve abstract from module
+ MAN3PODS => {},
+ AUTHOR => 'Brandon L. Black <blblack@gmail.com>');
diff --git a/ext/mro/mro.pm b/ext/mro/mro.pm
new file mode 100644
index 0000000000..5f0ae9683e
--- /dev/null
+++ b/ext/mro/mro.pm
@@ -0,0 +1,382 @@
+# mro.pm
+#
+# Copyright (c) 2007 Brandon L Black
+# Copyright (c) 2008 Larry Wall and others
+#
+# You may distribute under the terms of either the GNU General Public
+# License or the Artistic License, as specified in the README file.
+#
+package mro;
+use strict;
+use warnings;
+
+# mro.pm versions < 1.00 reserved for MRO::Compat
+# for partial back-compat to 5.[68].x
+our $VERSION = '1.01';
+
+sub import {
+ mro::set_mro(scalar(caller), $_[1]) if $_[1];
+}
+
+package # hide me from PAUSE
+ next;
+
+sub can { mro::_nextcan($_[0], 0) }
+
+sub method {
+ my $method = mro::_nextcan($_[0], 1);
+ goto &$method;
+}
+
+package # hide me from PAUSE
+ maybe::next;
+
+sub method {
+ my $method = mro::_nextcan($_[0], 0);
+ goto &$method if defined $method;
+ return;
+}
+
+require XSLoader;
+XSLoader::load('mro', $VERSION);
+
+1;
+
+__END__
+
+=head1 NAME
+
+mro - Method Resolution Order
+
+=head1 SYNOPSIS
+
+ use mro; # enables next::method and friends globally
+
+ use mro 'dfs'; # enable DFS MRO for this class (Perl default)
+ use mro 'c3'; # enable C3 MRO for this class
+
+=head1 DESCRIPTION
+
+The "mro" namespace provides several utilities for dealing
+with method resolution order and method caching in general.
+
+These interfaces are only available in Perl 5.9.5 and higher.
+See L<MRO::Compat> on CPAN for a mostly forwards compatible
+implementation for older Perls.
+
+=head1 OVERVIEW
+
+It's possible to change the MRO of a given class either by using C<use
+mro> as shown in the synopsis, or by using the L</mro::set_mro> function
+below. The functions in the mro namespace do not require loading the
+C<mro> module, as they are actually provided by the core perl interpreter.
+
+The special methods C<next::method>, C<next::can>, and
+C<maybe::next::method> are not available until this C<mro> module
+has been loaded via C<use> or C<require>.
+
+=head1 The C3 MRO
+
+In addition to the traditional Perl default MRO (depth first
+search, called C<DFS> here), Perl now offers the C3 MRO as
+well. Perl's support for C3 is based on the work done in
+Stevan Little's module L<Class::C3>, and most of the C3-related
+documentation here is ripped directly from there.
+
+=head2 What is C3?
+
+C3 is the name of an algorithm which aims to provide a sane method
+resolution order under multiple inheritance. It was first introduced in
+the language Dylan (see links in the L</"SEE ALSO"> section), and then
+later adopted as the preferred MRO (Method Resolution Order) for the
+new-style classes in Python 2.3. Most recently it has been adopted as the
+"canonical" MRO for Perl 6 classes, and the default MRO for Parrot objects
+as well.
+
+=head2 How does C3 work
+
+C3 works by always preserving local precendence ordering. This essentially
+means that no class will appear before any of its subclasses. Take, for
+instance, the classic diamond inheritance pattern:
+
+ <A>
+ / \
+ <B> <C>
+ \ /
+ <D>
+
+The standard Perl 5 MRO would be (D, B, A, C). The result being that B<A>
+appears before B<C>, even though B<C> is the subclass of B<A>. The C3 MRO
+algorithm however, produces the following order: (D, B, C, A), which does
+not have this issue.
+
+This example is fairly trivial; for more complex cases and a deeper
+explanation, see the links in the L</"SEE ALSO"> section.
+
+=head1 Functions
+
+=head2 mro::get_linear_isa($classname[, $type])
+
+Returns an arrayref which is the linearized MRO of the given class.
+Uses whichever MRO is currently in effect for that class by default,
+or the given MRO (either C<c3> or C<dfs> if specified as C<$type>).
+
+The linearized MRO of a class is an ordered array of all of the
+classes one would search when resolving a method on that class,
+starting with the class itself.
+
+If the requested class doesn't yet exist, this function will still
+succeed, and return C<[ $classname ]>
+
+Note that C<UNIVERSAL> (and any members of C<UNIVERSAL>'s MRO) are not
+part of the MRO of a class, even though all classes implicitly inherit
+methods from C<UNIVERSAL> and its parents.
+
+=head2 mro::set_mro($classname, $type)
+
+Sets the MRO of the given class to the C<$type> argument (either
+C<c3> or C<dfs>).
+
+=head2 mro::get_mro($classname)
+
+Returns the MRO of the given class (either C<c3> or C<dfs>).
+
+=head2 mro::get_isarev($classname)
+
+Gets the C<mro_isarev> for this class, returned as an
+arrayref of class names. These are every class that "isa"
+the given class name, even if the isa relationship is
+indirect. This is used internally by the MRO code to
+keep track of method/MRO cache invalidations.
+
+Currently, this list only grows, it never shrinks. This
+was a performance consideration (properly tracking and
+deleting isarev entries when someone removes an entry
+from an C<@ISA> is costly, and it doesn't happen often
+anyways). The fact that a class which no longer truly
+"isa" this class at runtime remains on the list should be
+considered a quirky implementation detail which is subject
+to future change. It shouldn't be an issue as long as
+you're looking at this list for the same reasons the
+core code does: as a performance optimization
+over having to search every class in existence.
+
+As with C<mro::get_mro> above, C<UNIVERSAL> is special.
+C<UNIVERSAL> (and parents') isarev lists do not include
+every class in existence, even though all classes are
+effectively descendants for method inheritance purposes.
+
+=head2 mro::is_universal($classname)
+
+Returns a boolean status indicating whether or not
+the given classname is either C<UNIVERSAL> itself,
+or one of C<UNIVERSAL>'s parents by C<@ISA> inheritance.
+
+Any class for which this function returns true is
+"universal" in the sense that all classes potentially
+inherit methods from it.
+
+For similar reasons to C<isarev> above, this flag is
+permanent. Once it is set, it does not go away, even
+if the class in question really isn't universal anymore.
+
+=head2 mro::invalidate_all_method_caches()
+
+Increments C<PL_sub_generation>, which invalidates method
+caching in all packages.
+
+=head2 mro::method_changed_in($classname)
+
+Invalidates the method cache of any classes dependent on the
+given class. This is not normally necessary. The only
+known case where pure perl code can confuse the method
+cache is when you manually install a new constant
+subroutine by using a readonly scalar value, like the
+internals of L<constant> do. If you find another case,
+please report it so we can either fix it or document
+the exception here.
+
+=head2 mro::get_pkg_gen($classname)
+
+Returns an integer which is incremented every time a
+real local method in the package C<$classname> changes,
+or the local C<@ISA> of C<$classname> is modified.
+
+This is intended for authors of modules which do lots
+of class introspection, as it allows them to very quickly
+check if anything important about the local properties
+of a given class have changed since the last time they
+looked. It does not increment on method/C<@ISA>
+changes in superclasses.
+
+It's still up to you to seek out the actual changes,
+and there might not actually be any. Perhaps all
+of the changes since you last checked cancelled each
+other out and left the package in the state it was in
+before.
+
+This integer normally starts off at a value of C<1>
+when a package stash is instantiated. Calling it
+on packages whose stashes do not exist at all will
+return C<0>. If a package stash is completely
+deleted (not a normal occurence, but it can happen
+if someone does something like C<undef %PkgName::>),
+the number will be reset to either C<0> or C<1>,
+depending on how completely package was wiped out.
+
+=head2 next::method
+
+This is somewhat like C<SUPER>, but it uses the C3 method
+resolution order to get better consistency in multiple
+inheritance situations. Note that while inheritance in
+general follows whichever MRO is in effect for the
+given class, C<next::method> only uses the C3 MRO.
+
+One generally uses it like so:
+
+ sub some_method {
+ my $self = shift;
+ my $superclass_answer = $self->next::method(@_);
+ return $superclass_answer + 1;
+ }
+
+Note that you don't (re-)specify the method name.
+It forces you to always use the same method name
+as the method you started in.
+
+It can be called on an object or a class, of course.
+
+The way it resolves which actual method to call is:
+
+=over 4
+
+=item 1
+
+First, it determines the linearized C3 MRO of
+the object or class it is being called on.
+
+=item 2
+
+Then, it determines the class and method name
+of the context it was invoked from.
+
+=item 3
+
+Finally, it searches down the C3 MRO list until
+it reaches the contextually enclosing class, then
+searches further down the MRO list for the next
+method with the same name as the contextually
+enclosing method.
+
+=back
+
+Failure to find a next method will result in an
+exception being thrown (see below for alternatives).
+
+This is substantially different than the behavior
+of C<SUPER> under complex multiple inheritance.
+(This becomes obvious when one realizes that the
+common superclasses in the C3 linearizations of
+a given class and one of its parents will not
+always be ordered the same for both.)
+
+B<Caveat>: Calling C<next::method> from methods defined outside the class:
+
+There is an edge case when using C<next::method> from within a subroutine
+which was created in a different module than the one it is called from. It
+sounds complicated, but it really isn't. Here is an example which will not
+work correctly:
+
+ *Foo::foo = sub { (shift)->next::method(@_) };
+
+The problem exists because the anonymous subroutine being assigned to the
+C<*Foo::foo> glob will show up in the call stack as being called
+C<__ANON__> and not C<foo> as you might expect. Since C<next::method> uses
+C<caller> to find the name of the method it was called in, it will fail in
+this case.
+
+But fear not, there's a simple solution. The module C<Sub::Name> will
+reach into the perl internals and assign a name to an anonymous subroutine
+for you. Simply do this:
+
+ use Sub::Name 'subname';
+ *Foo::foo = subname 'Foo::foo' => sub { (shift)->next::method(@_) };
+
+and things will Just Work.
+
+=head2 next::can
+
+This is similar to C<next::method>, but just returns either a code
+reference or C<undef> to indicate that no further methods of this name
+exist.
+
+=head2 maybe::next::method
+
+In simple cases, it is equivalent to:
+
+ $self->next::method(@_) if $self->next::can;
+
+But there are some cases where only this solution
+works (like C<goto &maybe::next::method>);
+
+=head1 SEE ALSO
+
+=head2 The original Dylan paper
+
+=over 4
+
+=item L<http://www.webcom.com/haahr/dylan/linearization-oopsla96.html>
+
+=back
+
+=head2 The prototype Perl 6 Object Model uses C3
+
+=over 4
+
+=item L<http://svn.openfoundry.org/pugs/perl5/Perl6-MetaModel/>
+
+=back
+
+=head2 Parrot now uses C3
+
+=over 4
+
+=item L<http://aspn.activestate.com/ASPN/Mail/Message/perl6-internals/2746631>
+
+=item L<http://use.perl.org/~autrijus/journal/25768>
+
+=back
+
+=head2 Python 2.3 MRO related links
+
+=over 4
+
+=item L<http://www.python.org/2.3/mro.html>
+
+=item L<http://www.python.org/2.2.2/descrintro.html#mro>
+
+=back
+
+=head2 C3 for TinyCLOS
+
+=over 4
+
+=item L<http://www.call-with-current-continuation.org/eggs/c3.html>
+
+=back
+
+=head2 Class::C3
+
+=over 4
+
+=item L<Class::C3>
+
+=back
+
+=head1 AUTHOR
+
+Brandon L. Black, E<lt>blblack@gmail.comE<gt>
+
+Based on Stevan Little's L<Class::C3>
+
+=cut
diff --git a/ext/mro/mro.xs b/ext/mro/mro.xs
new file mode 100644
index 0000000000..30f0d11302
--- /dev/null
+++ b/ext/mro/mro.xs
@@ -0,0 +1,439 @@
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+static AV*
+S_mro_get_linear_isa_c3(pTHX_ HV* stash, U32 level);
+
+static const struct mro_alg c3_alg =
+ {S_mro_get_linear_isa_c3, "c3", 2, 0, 0};
+
+/*
+=for apidoc mro_get_linear_isa_c3
+
+Returns the C3 linearization of @ISA
+the given stash. The return value is a read-only AV*.
+C<level> should be 0 (it is used internally in this
+function's recursion).
+
+You are responsible for C<SvREFCNT_inc()> on the
+return value if you plan to store it anywhere
+semi-permanently (otherwise it might be deleted
+out from under you the next time the cache is
+invalidated).
+
+=cut
+*/
+
+static AV*
+S_mro_get_linear_isa_c3(pTHX_ HV* stash, U32 level)
+{
+ AV* retval;
+ GV** gvp;
+ GV* gv;
+ AV* isa;
+ const HEK* stashhek;
+ struct mro_meta* meta;
+
+ assert(HvAUX(stash));
+
+ stashhek = HvNAME_HEK(stash);
+ if (!stashhek)
+ Perl_croak(aTHX_ "Can't linearize anonymous symbol table");
+
+ if (level > 100)
+ Perl_croak(aTHX_ "Recursive inheritance detected in package '%s'",
+ HEK_KEY(stashhek));
+
+ meta = HvMROMETA(stash);
+
+ /* return cache if valid */
+ if((retval = MUTABLE_AV(MRO_GET_PRIVATE_DATA(meta, &c3_alg)))) {
+ return retval;
+ }
+
+ /* not in cache, make a new one */
+
+ gvp = (GV**)hv_fetchs(stash, "ISA", FALSE);
+ isa = (gvp && (gv = *gvp) && isGV_with_GP(gv)) ? GvAV(gv) : NULL;
+
+ /* For a better idea how the rest of this works, see the much clearer
+ pure perl version in Algorithm::C3 0.01:
+ http://search.cpan.org/src/STEVAN/Algorithm-C3-0.01/lib/Algorithm/C3.pm
+ (later versions go about it differently than this code for speed reasons)
+ */
+
+ if(isa && AvFILLp(isa) >= 0) {
+ SV** seqs_ptr;
+ I32 seqs_items;
+ HV* const tails = MUTABLE_HV(sv_2mortal(MUTABLE_SV(newHV())));
+ AV *const seqs = MUTABLE_AV(sv_2mortal(MUTABLE_SV(newAV())));
+ I32* heads;
+
+ /* This builds @seqs, which is an array of arrays.
+ The members of @seqs are the MROs of
+ the members of @ISA, followed by @ISA itself.
+ */
+ I32 items = AvFILLp(isa) + 1;
+ SV** isa_ptr = AvARRAY(isa);
+ while(items--) {
+ SV* const isa_item = *isa_ptr++;
+ HV* const isa_item_stash = gv_stashsv(isa_item, 0);
+ if(!isa_item_stash) {
+ /* if no stash, make a temporary fake MRO
+ containing just itself */
+ AV* const isa_lin = newAV();
+ av_push(isa_lin, newSVsv(isa_item));
+ av_push(seqs, MUTABLE_SV(isa_lin));
+ }
+ else {
+ /* recursion */
+ AV* const isa_lin
+ = S_mro_get_linear_isa_c3(aTHX_ isa_item_stash, level + 1);
+ av_push(seqs, SvREFCNT_inc_simple_NN(MUTABLE_SV(isa_lin)));
+ }
+ }
+ av_push(seqs, SvREFCNT_inc_simple_NN(MUTABLE_SV(isa)));
+
+ /* This builds "heads", which as an array of integer array
+ indices, one per seq, which point at the virtual "head"
+ of the seq (initially zero) */
+ Newxz(heads, AvFILLp(seqs)+1, I32);
+
+ /* This builds %tails, which has one key for every class
+ mentioned in the tail of any sequence in @seqs (tail meaning
+ everything after the first class, the "head"). The value
+ is how many times this key appears in the tails of @seqs.
+ */
+ seqs_ptr = AvARRAY(seqs);
+ seqs_items = AvFILLp(seqs) + 1;
+ while(seqs_items--) {
+ AV *const seq = MUTABLE_AV(*seqs_ptr++);
+ I32 seq_items = AvFILLp(seq);
+ if(seq_items > 0) {
+ SV** seq_ptr = AvARRAY(seq) + 1;
+ while(seq_items--) {
+ SV* const seqitem = *seq_ptr++;
+ /* LVALUE fetch will create a new undefined SV if necessary
+ */
+ HE* const he = hv_fetch_ent(tails, seqitem, 1, 0);
+ if(he) {
+ SV* const val = HeVAL(he);
+ /* This will increment undef to 1, which is what we
+ want for a newly created entry. */
+ sv_inc(val);
+ }
+ }
+ }
+ }
+
+ /* Initialize retval to build the return value in */
+ retval = newAV();
+ av_push(retval, newSVhek(stashhek)); /* us first */
+
+ /* This loop won't terminate until we either finish building
+ the MRO, or get an exception. */
+ while(1) {
+ SV* cand = NULL;
+ SV* winner = NULL;
+ int s;
+
+ /* "foreach $seq (@seqs)" */
+ SV** const avptr = AvARRAY(seqs);
+ for(s = 0; s <= AvFILLp(seqs); s++) {
+ SV** svp;
+ AV * const seq = MUTABLE_AV(avptr[s]);
+ SV* seqhead;
+ if(!seq) continue; /* skip empty seqs */
+ svp = av_fetch(seq, heads[s], 0);
+ seqhead = *svp; /* seqhead = head of this seq */
+ if(!winner) {
+ HE* tail_entry;
+ SV* val;
+ /* if we haven't found a winner for this round yet,
+ and this seqhead is not in tails (or the count
+ for it in tails has dropped to zero), then this
+ seqhead is our new winner, and is added to the
+ final MRO immediately */
+ cand = seqhead;
+ if((tail_entry = hv_fetch_ent(tails, cand, 0, 0))
+ && (val = HeVAL(tail_entry))
+ && (SvIVX(val) > 0))
+ continue;
+ winner = newSVsv(cand);
+ av_push(retval, winner);
+ /* note however that even when we find a winner,
+ we continue looping over @seqs to do housekeeping */
+ }
+ if(!sv_cmp(seqhead, winner)) {
+ /* Once we have a winner (including the iteration
+ where we first found him), inc the head ptr
+ for any seq which had the winner as a head,
+ NULL out any seq which is now empty,
+ and adjust tails for consistency */
+
+ const int new_head = ++heads[s];
+ if(new_head > AvFILLp(seq)) {
+ SvREFCNT_dec(avptr[s]);
+ avptr[s] = NULL;
+ }
+ else {
+ HE* tail_entry;
+ SV* val;
+ /* Because we know this new seqhead used to be
+ a tail, we can assume it is in tails and has
+ a positive value, which we need to dec */
+ svp = av_fetch(seq, new_head, 0);
+ seqhead = *svp;
+ tail_entry = hv_fetch_ent(tails, seqhead, 0, 0);
+ val = HeVAL(tail_entry);
+ sv_dec(val);
+ }
+ }
+ }
+
+ /* if we found no candidates, we are done building the MRO.
+ !cand means no seqs have any entries left to check */
+ if(!cand) {
+ Safefree(heads);
+ break;
+ }
+
+ /* If we had candidates, but nobody won, then the @ISA
+ hierarchy is not C3-incompatible */
+ if(!winner) {
+ /* we have to do some cleanup before we croak */
+
+ SvREFCNT_dec(retval);
+ Safefree(heads);
+
+ Perl_croak(aTHX_ "Inconsistent hierarchy during C3 merge of class '%s': "
+ "merging failed on parent '%"SVf"'", HEK_KEY(stashhek), SVfARG(cand));
+ }
+ }
+ }
+ else { /* @ISA was undefined or empty */
+ /* build a retval containing only ourselves */
+ retval = newAV();
+ av_push(retval, newSVhek(stashhek));
+ }
+
+ /* we don't want anyone modifying the cache entry but us,
+ and we do so by replacing it completely */
+ SvREADONLY_on(retval);
+
+ return MUTABLE_AV(Perl_mro_set_private_data(aTHX_ meta, &c3_alg,
+ MUTABLE_SV(retval)));
+ return retval;
+}
+
+
+/* These two are static helpers for next::method and friends,
+ and re-implement a bunch of the code from pp_caller() in
+ a more efficient manner for this particular usage.
+*/
+
+static I32
+__dopoptosub_at(const PERL_CONTEXT *cxstk, I32 startingblock) {
+ I32 i;
+ for (i = startingblock; i >= 0; i--) {
+ if(CxTYPE((PERL_CONTEXT*)(&cxstk[i])) == CXt_SUB) return i;
+ }
+ return i;
+}
+
+MODULE = mro PACKAGE = mro PREFIX = mro
+
+void
+mro_nextcan(...)
+ PREINIT:
+ SV* self = ST(0);
+ const I32 throw_nomethod = SvIVX(ST(1));
+ register I32 cxix = cxstack_ix;
+ register const PERL_CONTEXT *ccstack = cxstack;
+ const PERL_SI *top_si = PL_curstackinfo;
+ HV* selfstash;
+ SV *stashname;
+ const char *fq_subname;
+ const char *subname;
+ STRLEN stashname_len;
+ STRLEN subname_len;
+ SV* sv;
+ GV** gvp;
+ AV* linear_av;
+ SV** linear_svp;
+ const char *hvname;
+ I32 entries;
+ struct mro_meta* selfmeta;
+ HV* nmcache;
+ I32 i;
+ PPCODE:
+ PERL_UNUSED_ARG(cv);
+
+ if(sv_isobject(self))
+ selfstash = SvSTASH(SvRV(self));
+ else
+ selfstash = gv_stashsv(self, GV_ADD);
+
+ assert(selfstash);
+
+ hvname = HvNAME_get(selfstash);
+ if (!hvname)
+ Perl_croak(aTHX_ "Can't use anonymous symbol table for method lookup");
+
+ /* This block finds the contextually-enclosing fully-qualified subname,
+ much like looking at (caller($i))[3] until you find a real sub that
+ isn't ANON, etc (also skips over pureperl next::method, etc) */
+ for(i = 0; i < 2; i++) {
+ cxix = __dopoptosub_at(ccstack, cxix);
+ for (;;) {
+ GV* cvgv;
+ STRLEN fq_subname_len;
+
+ /* we may be in a higher stacklevel, so dig down deeper */
+ while (cxix < 0) {
+ if(top_si->si_type == PERLSI_MAIN)
+ Perl_croak(aTHX_ "next::method/next::can/maybe::next::method must be used in method context");
+ top_si = top_si->si_prev;
+ ccstack = top_si->si_cxstack;
+ cxix = __dopoptosub_at(ccstack, top_si->si_cxix);
+ }
+
+ if(CxTYPE((PERL_CONTEXT*)(&ccstack[cxix])) != CXt_SUB
+ || (PL_DBsub && GvCV(PL_DBsub) && ccstack[cxix].blk_sub.cv == GvCV(PL_DBsub))) {
+ cxix = __dopoptosub_at(ccstack, cxix - 1);
+ continue;
+ }
+
+ {
+ const I32 dbcxix = __dopoptosub_at(ccstack, cxix - 1);
+ if (PL_DBsub && GvCV(PL_DBsub) && dbcxix >= 0 && ccstack[dbcxix].blk_sub.cv == GvCV(PL_DBsub)) {
+ if(CxTYPE((PERL_CONTEXT*)(&ccstack[dbcxix])) != CXt_SUB) {
+ cxix = dbcxix;
+ continue;
+ }
+ }
+ }
+
+ cvgv = CvGV(ccstack[cxix].blk_sub.cv);
+
+ if(!isGV(cvgv)) {
+ cxix = __dopoptosub_at(ccstack, cxix - 1);
+ continue;
+ }
+
+ /* we found a real sub here */
+ sv = sv_2mortal(newSV(0));
+
+ gv_efullname3(sv, cvgv, NULL);
+
+ fq_subname = SvPVX(sv);
+ fq_subname_len = SvCUR(sv);
+
+ subname = strrchr(fq_subname, ':');
+ if(!subname)
+ Perl_croak(aTHX_ "next::method/next::can/maybe::next::method cannot find enclosing method");
+
+ subname++;
+ subname_len = fq_subname_len - (subname - fq_subname);
+ if(subname_len == 8 && strEQ(subname, "__ANON__")) {
+ cxix = __dopoptosub_at(ccstack, cxix - 1);
+ continue;
+ }
+ break;
+ }
+ cxix--;
+ }
+
+ /* If we made it to here, we found our context */
+
+ /* Initialize the next::method cache for this stash
+ if necessary */
+ selfmeta = HvMROMETA(selfstash);
+ if(!(nmcache = selfmeta->mro_nextmethod)) {
+ nmcache = selfmeta->mro_nextmethod = newHV();
+ }
+ else { /* Use the cached coderef if it exists */
+ HE* cache_entry = hv_fetch_ent(nmcache, sv, 0, 0);
+ if (cache_entry) {
+ SV* const val = HeVAL(cache_entry);
+ if(val == &PL_sv_undef) {
+ if(throw_nomethod)
+ Perl_croak(aTHX_ "No next::method '%s' found for %s", subname, hvname);
+ XSRETURN_EMPTY;
+ }
+ mXPUSHs(newRV_inc(val));
+ XSRETURN(1);
+ }
+ }
+
+ /* beyond here is just for cache misses, so perf isn't as critical */
+
+ stashname_len = subname - fq_subname - 2;
+ stashname = newSVpvn_flags(fq_subname, stashname_len, SVs_TEMP);
+
+ /* has ourselves at the top of the list */
+ linear_av = S_mro_get_linear_isa_c3(aTHX_ selfstash, 0);
+
+ linear_svp = AvARRAY(linear_av);
+ entries = AvFILLp(linear_av) + 1;
+
+ /* Walk down our MRO, skipping everything up
+ to the contextually enclosing class */
+ while (entries--) {
+ SV * const linear_sv = *linear_svp++;
+ assert(linear_sv);
+ if(sv_eq(linear_sv, stashname))
+ break;
+ }
+
+ /* Now search the remainder of the MRO for the
+ same method name as the contextually enclosing
+ method */
+ if(entries > 0) {
+ while (entries--) {
+ SV * const linear_sv = *linear_svp++;
+ HV* curstash;
+ GV* candidate;
+ CV* cand_cv;
+
+ assert(linear_sv);
+ curstash = gv_stashsv(linear_sv, FALSE);
+
+ if (!curstash) {
+ if (ckWARN(WARN_SYNTAX))
+ Perl_warner(aTHX_ packWARN(WARN_SYNTAX), "Can't locate package %"SVf" for @%s::ISA",
+ (void*)linear_sv, hvname);
+ continue;
+ }
+
+ assert(curstash);
+
+ gvp = (GV**)hv_fetch(curstash, subname, subname_len, 0);
+ if (!gvp) continue;
+
+ candidate = *gvp;
+ assert(candidate);
+
+ if (SvTYPE(candidate) != SVt_PVGV)
+ gv_init(candidate, curstash, subname, subname_len, TRUE);
+
+ /* Notably, we only look for real entries, not method cache
+ entries, because in C3 the method cache of a parent is not
+ valid for the child */
+ if (SvTYPE(candidate) == SVt_PVGV && (cand_cv = GvCV(candidate)) && !GvCVGEN(candidate)) {
+ SvREFCNT_inc_simple_void_NN(MUTABLE_SV(cand_cv));
+ (void)hv_store_ent(nmcache, newSVsv(sv), MUTABLE_SV(cand_cv), 0);
+ mXPUSHs(newRV_inc(MUTABLE_SV(cand_cv)));
+ XSRETURN(1);
+ }
+ }
+ }
+
+ (void)hv_store_ent(nmcache, newSVsv(sv), &PL_sv_undef, 0);
+ if(throw_nomethod)
+ Perl_croak(aTHX_ "No next::method '%s' found for %s", subname, hvname);
+ XSRETURN_EMPTY;
+
+BOOT:
+ Perl_mro_register(aTHX_ &c3_alg);
diff --git a/ext/mro/t/pluggable.t b/ext/mro/t/pluggable.t
new file mode 100644
index 0000000000..be3fe060fc
--- /dev/null
+++ b/ext/mro/t/pluggable.t
@@ -0,0 +1,26 @@
+#!perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 3;
+
+{
+ package A;
+}
+
+@B::ISA = 'A';
+@C::ISA = 'A';
+@D::ISA = qw(B C);
+
+eval {mro::set_mro('D', 'c3')};
+
+like $@, qr/Invalid mro name: 'c3'/;
+
+require mro;
+
+is_deeply(mro::get_linear_isa('D'), [qw(D B A C)], 'still dfs MRO');
+
+mro::set_mro('D', 'c3');
+
+is_deeply(mro::get_linear_isa('D'), [qw(D B C A)], 'c3 MRO');