more tests
[gitmo/MRO-Compat.git] / lib / MRO / Compat.pm
CommitLineData
399a1dc7 1package MRO::Compat;
4c4e4170 2use strict;
3use warnings;
e9b18837 4require 5.006_000;
4c4e4170 5
763002b3 6# Keep this < 1.00, so people can tell the fake
7# mro.pm from the real one
4c4e4170 8our $VERSION = '0.01';
9
4c4e4170 10BEGIN {
e9b18837 11 # Alias our private functions over to
12 # the mro:: namespace and load
13 # Class::C3 if Perl < 5.9.5
399a1dc7 14 if($] < 5.009_005) {
e9b18837 15 require Class::C3;
399a1dc7 16 *mro::import = \&__import;
17 *mro::get_linear_isa = \&__get_linear_isa;
18 *mro::set_mro = \&__set_mro;
19 *mro::get_mro = \&__get_mro;
20 *mro::get_isarev = \&__get_isarev;
21 *mro::is_universal = \&__is_universal;
22 *mro::method_changed_in = \&__method_changed_in;
23 *mro::invalidate_all_method_caches
24 = \&__invalidate_all_method_caches;
763002b3 25 $mro::VERSION = $VERSION;
26 $INC{'mro.pm'} = 'Faked by MRO::Compat';
4c4e4170 27 }
e9b18837 28
29 # Provide no-op Class::C3::.*initialize() funcs for 5.9.5+
30 else {
31 no warnings 'redefine';
32 *Class::C3::initialize = sub { 1 };
33 *Class::C3::reinitialize = sub { 1 };
34 *Class::C3::uninitialize = sub { 1 };
35 }
4c4e4170 36}
37
4c4e4170 38=head1 NAME
39
42915ba4 40MRO::Compat - mro::* interface compatibility for Perls < 5.9.5
4c4e4170 41
42=head1 SYNOPSIS
43
44 package FooClass; use base qw/X Y Z/;
45 package X; use base qw/ZZZ/;
46 package Y; use base qw/ZZZ/;
47 package Z; use base qw/ZZZ/;
48
49 package main;
399a1dc7 50 use MRO::Compat;
4c4e4170 51 my $linear = mro::get_linear_isa('FooClass');
399a1dc7 52 print join(q{, }, @$linear);
4c4e4170 53
54 # Prints: "FooClass, X, ZZZ, Y, Z"
55
56=head1 DESCRIPTION
57
58The "mro" namespace provides several utilities for dealing
399a1dc7 59with method resolution order and method caching in general
60in Perl 5.9.5 and higher.
4c4e4170 61
42915ba4 62This module provides those interfaces for
e9b18837 63earlier versions of Perl (back to 5.6.0 anyways).
64
42915ba4 65It is a harmless no-op to use this module on 5.9.5+. If
66you're writing a piece of software that would like to use
67the parts of 5.9.5+'s mro:: interfaces that are supported
e9b18837 68here, and you want compatibility with older Perls, this
69is the module for you.
4c4e4170 70
399a1dc7 71This module never exports any functions. All calls must
72be fully qualified with the C<mro::> prefix.
4c4e4170 73
74=head1 Functions
75
76=head2 mro::get_linear_isa($classname[, $type])
77
78Returns an arrayref which is the linearized MRO of the given class.
79Uses whichever MRO is currently in effect for that class by default,
80or the given MRO (either C<c3> or C<dfs> if specified as C<$type>).
81
82The linearized MRO of a class is a single ordered list of all of the
83classes that would be visited in the process of resolving a method
84on the given class, starting with itself. It does not include any
85duplicate entries.
86
4c4e4170 87Note that C<UNIVERSAL> (and any members of C<UNIVERSAL>'s MRO) are not
88part of the MRO of a class, even though all classes implicitly inherit
89methods from C<UNIVERSAL> and its parents.
90
399a1dc7 91=cut
92
ed71cabb 93sub __get_linear_isa_dfs {
94 no strict 'refs';
95
96 my $classname = shift;
97
98 my @lin = ($classname);
99 my %stored;
100 foreach my $parent (@{"$classname\::ISA"}) {
101 my $plin = __get_linear_isa_dfs($parent);
102 foreach (@$plin) {
103 next if exists $stored{$_};
104 push(@lin, $_);
105 $stored{$_} = 1;
106 }
107 }
108 return \@lin;
109}
110
399a1dc7 111sub __get_linear_isa {
ed71cabb 112 my ($classname, $type) = @_;
113 die "mro::get_mro requires a classname" if !$classname;
114
115 $type ||= __get_mro($classname);
116 if($type eq 'dfs') {
117 return __get_linear_isa_dfs($classname);
118 }
119 elsif($type eq 'c3') {
120 return [Class::C3::calculateMRO($classname)];
121 }
122 die "type argument must be 'dfs' or 'c3'";
399a1dc7 123}
124
125=head2 mro::import
126
e9b18837 127This allows the C<use mro 'dfs'> and
128C<use mro 'c3'> syntaxes, providing you
129L<use MRO::Compat> first. Please see the
130L</USING C3> section for additional details.
399a1dc7 131
132=cut
133
134sub __import {
e9b18837 135 if($_[1]) {
136 goto &Class::C3::import if $_[1] eq 'c3';
137 __set_mro(scalar(caller), $_[1]);
138 }
399a1dc7 139}
140
4c4e4170 141=head2 mro::set_mro($classname, $type)
142
e9b18837 143Sets the mro of C<$classname> to one of the types
144C<dfs> or C<c3>. Please see the L</USING C3>
145section for additional details.
399a1dc7 146
147=cut
148
149sub __set_mro {
e9b18837 150 my ($classname, $type) = @_;
151 if(!$classname || !$type) {
152 die q{Usage: mro::set_mro($classname, $type)};
153 }
154 if($type eq 'c3') {
155 eval "package $classname; use Class::C3";
156 die $@ if $@;
157 }
158 if($type ne 'dfs') {
159 die q{Invalid mro type "$type"};
160 }
161
763002b3 162 # In the dfs case, check whether we need to undo C3
e9b18837 163 if(defined $Class::C3::MRO{$classname}) {
164 Class::C3::_remove_method_dispatch_table($classname);
165 }
166 delete $Class::C3::MRO{$classname};
167
168 return;
399a1dc7 169}
4c4e4170 170
171=head2 mro::get_mro($classname)
172
173Returns the MRO of the given class (either C<c3> or C<dfs>).
174
e9b18837 175It considers any Class::C3-using class to have C3 MRO
176even before L<Class::C3::initialize()> is called.
177
399a1dc7 178=cut
179
180sub __get_mro {
ed71cabb 181 my $classname = shift;
399a1dc7 182 die "mro::get_mro requires a classname" if !$classname;
e9b18837 183 return 'c3' if exists $Class::C3::MRO{$classname};
399a1dc7 184 return 'dfs';
185}
186
4c4e4170 187=head2 mro::get_isarev($classname)
188
42915ba4 189Returns an array of classes who are subclasses of the
190given classname. In other words, classes who we exists,
191however indirectly, in the @ISA inheritancy hierarchy of.
192
193This is much slower on pre-5.9.5 Perls with MRO::Compat
194than it is on 5.9.5+, as it has to search the entire
195package namespace.
399a1dc7 196
197=cut
198
42915ba4 199sub __get_all_pkgs_with_isas {
200 no strict 'refs';
201 no warnings 'recursion';
202
203 my @retval;
204
205 my $search = shift;
206 my $pfx;
207 my $isa;
208 if($search) {
209 $isa = \@{"$search\::ISA"};
210 $pfx = "$search\::";
211 }
212 else {
213 $search = 'main';
214 $isa = \@main::ISA;
215 $pfx = '';
216 }
217
218 push(@retval, $search) if scalar(@$isa);
219
220 foreach my $cand (keys %{"$search\::"}) {
221 if($cand =~ /::$/) {
222 $cand =~ s/::$//;
223 next if $cand eq $search; # skip self-reference (main?)
224 push(@retval, @{__get_all_pkgs_with_isas($pfx . $cand)});
225 }
226 }
227
228 return \@retval;
229}
230
231sub __get_isarev_recurse {
232 no strict 'refs';
233
234 my ($class, $all_isas, $level) = @_;
235
236 die "Recursive inheritance detected" if $level > 100;
237
238 my %retval;
239
240 foreach my $cand (@$all_isas) {
241 my $found_me;
242 foreach (@{"$cand\::ISA"}) {
243 if($_ eq $class) {
244 $found_me = 1;
245 last;
246 }
247 }
248 if($found_me) {
249 $retval{$cand} = 1;
250 map { $retval{$_} = 1 }
251 @{__get_isarev_recurse($cand, $all_isas, $level+1)};
252 }
253 }
254 return [keys %retval];
255}
256
399a1dc7 257sub __get_isarev {
42915ba4 258 my $classname = shift;
259 die "mro::get_isarev requires a classname" if !$classname;
260
d029d565 261 @{__get_isarev_recurse($classname, __get_all_pkgs_with_isas(), 0)};
399a1dc7 262}
4c4e4170 263
264=head2 mro::is_universal($classname)
265
266Returns a boolean status indicating whether or not
267the given classname is either C<UNIVERSAL> itself,
268or one of C<UNIVERSAL>'s parents by C<@ISA> inheritance.
269
270Any class for which this function returns true is
271"universal" in the sense that all classes potentially
272inherit methods from it.
273
399a1dc7 274=cut
275
276sub __is_universal {
277 my $classname = shift;
278 die "mro::is_universal requires a classname" if !$classname;
279
ac5a5a7f 280 my $lin = __get_linear_isa('UNIVERSAL');
399a1dc7 281 foreach (@$lin) {
282 return 1 if $classname eq $_;
283 }
284
285 return 0;
286}
287
288=head2 mro::invalidate_all_method_caches
4c4e4170 289
290Increments C<PL_sub_generation>, which invalidates method
291caching in all packages.
292
399a1dc7 293=cut
294
295sub __invalidate_all_method_caches {
296 # Super secret mystery code :)
297 @fedcba98::ISA = @fedcba98::ISA;
298 return;
299}
300
4c4e4170 301=head2 mro::method_changed_in($classname)
302
303Invalidates the method cache of any classes dependent on the
399a1dc7 304given class. In L<MRO::Compat> on pre-5.9.5 Perls, this is
305an alias for C<mro::invalidate_all_method_caches> above, as
306pre-5.9.5 Perls have no other way to do this. It will still
307enforce the requirement that you pass it a classname, for
308compatibility.
309
310=cut
311
312sub __method_changed_in {
313 my $classname = shift;
314 die "mro::method_changed_in requires a classname" if !$classname;
315
316 __invalidate_all_method_caches();
317}
4c4e4170 318
e9b18837 319=head1 USING C3
320
321While this module makes the 5.9.5+ syntaxes
322C<use mro 'c3'> and C<mro::set_mro("Foo", 'c3')> available
323on older Perls, it does so merely by passing off the work
324to L<Class::C3>.
325
326It does not remove the need for you to call
327L<Class::C3>'s C<initialize()>, C<reinitialize()>, and/or
328C<uninitialize()> at the appropriate times
329as documented in the L<Class::C3> docs.
330
331Because L<MRO::Compat> has L<Class::C3> as a pre-requisite,
332and requires it at C<use> time, you can blindly call
333those functions in code that uses L<MRO::Compat>.
334Under 5.9.5+ with L<MRO::Compat>, your calls to those
335functions will become a no-op and everything will work fine.
336
4c4e4170 337=head1 SEE ALSO
338
339L<Class::C3>
340
399a1dc7 341L<mro>
342
4c4e4170 343=head1 AUTHOR
344
345Brandon L. Black, E<lt>blblack@gmail.comE<gt>
346
347=head1 COPYRIGHT AND LICENSE
348
349Copyright 2007 Brandon L. Black E<lt>blblack@gmail.comE<gt>
350
351This library is free software; you can redistribute it and/or modify
352it under the same terms as Perl itself.
353
354=cut
355
3561;