File Coverage

blib/lib/Class/Multimethods.pm
Criterion Covered Total %
statement 216 637 33.9
branch 57 302 18.8
condition 55 195 28.2
subroutine 22 35 62.8
pod 1 17 5.8
total 351 1186 29.6


line stmt bran cond sub pod time code
1             package Class::Multimethods;
2              
3 1     1   1012 use strict;
  1         1  
  1         34  
4 1     1   6 use vars qw($VERSION @ISA @EXPORT);
  1         2  
  1         67  
5 1     1   6 use Carp;
  1         4  
  1         119  
6              
7             require Exporter;
8             @ISA = qw(Exporter);
9             @EXPORT = qw( multimethod resolve_ambiguous resolve_no_match superclass
10             multimethod_wrapper );
11             $VERSION = '1.70';
12              
13 1         1244 use vars qw(%dispatch %cached %hasgeneric
14             %ambiguous_handler %no_match_handler
15 1     1   4 %max_args %min_args);
  1         2  
16              
17             %dispatch = (); # THE DISPATCH TABLE
18             %cached = (); # THE CACHE OF PREVIOUS RESOLUTIONS OF EMPTY SLOTS
19             %hasgeneric = (); # WHETHER A GIVEN MULTIMETHOD HAS ANY GENERIC VARIANTS
20             %ambiguous_handler = (); # HANDLERS FOR AMBIGUOUS CALLS
21             %no_match_handler = (); # HANDLERS FOR AMBIGUOUS CALLS
22             %max_args = (); # RECORDS MAX NUM OF ARGS IN ANY VARIANT
23             %min_args = (); # RECORDS MIN NUM OF ARGS IN ANY VARIANT
24              
25              
26             # THIS IS INTERPOSED BETWEEN THE CALLING PACKAGE AND Exporter TO SUPPORT THE
27             # use Class:Multimethods @methodnames SYNTAX
28              
29             sub import
30             {
31 5     5   177 my $package = (caller)[0];
32 5         21 install_dispatch($package,pop @_) while $#_;
33 5         6248 Class::Multimethods->export_to_level(1);
34             }
35              
36              
37             # INSTALL A DISPATCHING SUB FOR THE NAMED MULTIMETHOD IN THE CALLING PACKAGE
38              
39             sub install_dispatch
40             {
41 28     28 0 49 my ($pkg, $name) = @_;
42             # eval "sub ${pkg}::$name { Class::Multimethods::dispatch('$name',\@_) }"
43 28 100 50 1   1563 eval(make_dispatch($pkg,$name)) || croak "internal error: $@"
  1 50 66 252   200  
  1 0 33 2   2  
  1 0 0 4   3  
  2 0 0 2   12  
  2 0 0 3   5  
  2 0 66 4   4  
  1 0 100 81   4  
  1 0 100     6  
  1 0 66     9  
  0 0 0     0  
  0 0 66     0  
  0 0 33     0  
  0 100 0     0  
  0 50 0     0  
  0 0 0     0  
  0 50 33     0  
  0 0 33     0  
  0 0 0     0  
  0 50 0     0  
  0 0 0     0  
  0 0 66     0  
  0 50 33     0  
  0 100 0     0  
  0 100 0     0  
  0 50 0     0  
  0 0 66     0  
  0 0 33     0  
  0 0 66     0  
  0 0 33     0  
  0 0 0     0  
  0 0 33     0  
  0 0 66     0  
  0 0 66     0  
  0 0 33     0  
  0 0 0     0  
  0 0 33     0  
  0 50 66     0  
  0 0 100     0  
  0 0 66     0  
  0 0 66     0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  252 0       84899  
  252 0       279  
  252 0       481  
  508 50       1087  
  508 0       531  
  508 0       781  
  252 0       364  
  252 0       1073  
  252 0       466  
  220 0       658  
  220 0       561  
  222 0       821  
  0 0       0  
  220 0       330  
  220 0       211  
  220 50       525  
  220 50       724  
  220 0       897  
  0 50       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 0       0  
  0 50       0  
  0 0       0  
  0 100       0  
  0 50       0  
  220 0       469  
  25 50       54  
  25 0       69  
  12 0       18  
  12 0       24  
  0 0       0  
  0 0       0  
  12 0       31  
  12 50       36  
  24 0       1294  
  183 100       228  
  183 50       317  
  0 50       0  
  0 100       0  
  183 0       432  
  183 0       19128  
  2 50       71  
  2 0       3  
  2 0       5  
  4 0       17  
  4 100       6  
  4 50       9  
  2         3  
  2         7  
  2         9  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  4         612  
  4         5  
  4         7  
  8         38  
  8         9  
  8         14  
  4         7  
  4         13  
  4         18  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  2         407  
  2         3  
  2         5  
  4         18  
  4         5  
  4         9  
  2         3  
  2         7  
  2         9  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  3         604  
  3         4  
  3         7  
  6         23  
  6         8  
  6         12  
  3         5  
  3         17  
  3         8  
  3         11  
  3         9  
  3         14  
  0         0  
  3         5  
  3         4  
  3         9  
  3         12  
  3         12  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  3         6  
  3         8  
  3         7  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  4         587  
  4         7  
  4         7  
  8         39  
  8         10  
  8         12  
  4         9  
  4         14  
  4         15  
  1         4  
  1         130  
  1         7  
  0         0  
  1         3  
  1         2  
  1         4  
  1         4  
  1         7  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         4  
  1         3  
  1         4  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  81         27933  
  81         252  
  81         179  
  162         341  
  162         180  
  162         643  
  81         125  
  81         363  
  81         142  
  79         235  
  79         207  
  79         307  
  0         0  
  79         554  
  79         89  
  79         180  
  79         408  
  79         331  
  61         411  
  61         137  
  61         72  
  61         135  
  61         117  
  122         118  
  122         277  
  122         477  
  61         123  
  61         172  
  61         228  
  79         173  
  76         165  
  76         204  
  3         6  
  3         8  
  0         0  
  0         0  
  3         11  
  3         12  
  6         481  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
44             unless eval "defined \&${pkg}::$name";
45             }
46              
47             # REGISTER RESOLUTION FUNCTIONS FOR AMBIGUOUS AND NO-MATCH CALLS
48              
49             sub resolve_ambiguous
50             {
51 0     0 0 0 my $name = shift;
52 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'CODE')
53 0         0 { $ambiguous_handler{$name} = $_[0] }
54             else
55 0         0 { $ambiguous_handler{$name} = join ',', @_ }
56             }
57              
58             sub resolve_no_match
59             {
60 0     0 0 0 my $name = shift;
61 0 0 0     0 if (@_ == 1 && ref($_[0]) eq 'CODE')
62 0         0 { $no_match_handler{$name} = $_[0] }
63             else
64 0         0 { $no_match_handler{$name} = join ',', @_ }
65             }
66              
67             # GENERATE A SPECIAL PROXY OBJECT TO INDICATE THAT THE ANCESTOR OF AN OBJECT'S
68             # CLASS IS REQUIRED
69              
70             sub superclass
71             {
72 0     0 0 0 my ($obj, $super) = @_;
73 0 0 0     0 $super = ref($obj) || ( (~$obj&$obj) eq 0 ? '#' : '$' ) if @_ <= 1;
74 0 0       0 bless \$obj, (@_ > 1 )
75             ? "Class::Multimethods::SUPERCLASS_IS::$super"
76             : "Class::Multimethods::SUPERCLASS_OF::$super";
77             }
78              
79             sub _prettify
80             {
81 198 50   198   5002 $_[0] =~ s/Class::Multimethods::SUPERCLASS_IS:://
82             or $_[0] =~ s/Class::Multimethods::SUPERCLASS_OF::(.*)/superclass($1)/;
83             }
84              
85             # SQUIRREL AWAY THE PROFFERED SUB REF INDEXED BY THE MULTIMETHOD NAME
86             # AND THE TYPE NAMES SUPPLIED. CAN ALSO BE USED WITH JUST THE MULTIMETHOD
87             # NAME IN ORDER TO INSTALL A SUITABLE DISPATCH SUB INTO THE CALLING PACKAGE
88              
89             sub multimethod
90             {
91 27     27 1 2369 my $package = (caller)[0];
92 27         46 my $name = shift;
93 27         60 install_dispatch($package,$name);
94              
95 27 100       109 if (@_) # NOT JUST INSTALLING A DISPATCH SUB...
96             {
97 26         33 my $code = pop;
98 26 50       99 croak "multimethod: last arg must be a code reference"
99             unless ref($code) eq 'CODE';
100              
101 26         64 my @types = @_;
102              
103 26         58 for ($Class::Multimethods::max_args{$name})
104 26 100 100     158 { $_ = @types if !defined || @types > $_ }
105 26         57 for ($Class::Multimethods::min_args{$name})
106 26 100 66     130 { $_ = @types if !defined || @types < $_ }
107            
108 26         62 my $sig = join ',', @types;
109              
110 26   100     121 $Class::Multimethods::hasgeneric{$name} ||= $sig =~ /\*/;
111              
112 26 50 33     79 carp "Multimethod $name($sig) redefined"
113             if $^W && exists $dispatch{$name}{$sig};
114 26         71 $dispatch{$name}{$sig} = $code;
115              
116             # NOTE: ADDING A MULTIMETHOD COMPROMISES CACHING
117             # THIS IS A DUMB, BUT FAST, FIX...
118 26         97 $cached{$name} = {};
119             }
120             }
121              
122              
123             # THIS IS THE ACTUAL MEAT OF THE PACKAGE -- A GENERIC DISPATCHING SUB
124             # WHICH EXPLORES THE %dispatch AND %cache HASHES LOOKING FOR A UNIQUE
125             # BEST MATCH...
126              
127             sub make_dispatch # ($name)
128             {
129 8     8 0 14 my ($pkg,$name) = @_;
130 8         16 my $code = q{
131              
132             sub PACKAGE::NAME
133             {
134             # MAP THE ARGS TO TYPE NAMES, MAP VALUES TO '#' (FOR NUMBERS)
135             # OR '$' (OTHERWISE). THEN BUILD A FUNCTION TYPE SIGNATURE
136             # (LIKE A "PATH" INTO THE VARIOUS TABLES)
137              
138             my $sig = "";
139             my $nexttype;
140             foreach ( @_ )
141             {
142             $nexttype = ref || ( (~$_&$_) eq 0 ? '#' : '$' );
143             # $_ = $$_ if index($nexttype,'Class::Multimethods::SUPERCLASS')==0;
144             $sig .= $nexttype;
145             $sig .= ",";
146             }
147             chop $sig;
148              
149             my $code = $Class::Multimethods::dispatch{'NAME'}{$sig}
150             || $Class::Multimethods::cached{'NAME'}{$sig};
151            
152             return $code->(@_) if ($code);
153              
154             my @types = split /,/, $sig;
155             for (my $i=1; $i<@types; $i++)
156             {
157             $_[$i] = ${$_[$i]}
158             if index($types[$i],'Class::Multimethods::SUPERCLASS')==0;
159             }
160             my %tried = (); # USED TO AVOID MULTIPLE MATCHES ON SAME SIG
161             my @code; # STORES LIST OF EQUALLY-CLOSE MATCHING SUBS
162             my @candidates = ( [@types] ); # STORES POSSIBLE MATCHING SIGS
163              
164             # TRY AND RESOLVE TO AN TYPE-EXPLICIT SIGNATURE (USING INHERITANCE)
165              
166             1 until (Class::Multimethods::resolve('NAME',\@candidates,\@code,\%tried) || !@candidates);
167              
168             # IF THAT DOESN'T WORK, TRY A GENERIC SIGNATURE (IF THERE ARE ANY)
169             # THE NESTED LOOPS GENERATE ALL POSSIBLE PERMUTATIONS OF GENERIC
170             # SIGNATURES IN SUCH A WAY THAT, EACH TIME resolve IS CALLED, ALL
171             # THE CANDIDATES ARE EQUALLY GENERIC (HAVE AN EQUAL NUMBER OF GENERIC
172             # PLACEHOLDERS)
173              
174             if ( @code == 0 && $Class::Multimethods::hasgeneric{'NAME'} )
175             {
176             # TRY GENERIC VERSIONS
177             my @gencandidates = ([@types]);
178             GENERIC: for (0..$#types)
179             {
180             @candidates = ();
181             for (my $gci=0; $gci<@gencandidates; $gci++)
182             {
183             for (my $i=0; $i<@types; $i++)
184             {
185             push @candidates,
186             [@{$gencandidates[$gci]}];
187             $candidates[-1][$i] = "*";
188             }
189             }
190             @gencandidates = @candidates;
191             1 until (Class::Multimethods::resolve('NAME',\@candidates,\@code,\%tried) || !@candidates);
192             last GENERIC if @code;
193             }
194             }
195              
196             # RESOLUTION PROCESS COMPLETED...
197             # IF EXACTLY ONE BEST MATCH, CALL IT...
198              
199             if ( @code == 1 )
200             {
201             $Class::Multimethods::cached{'NAME'}{$sig} = $code[0];
202             return $code[0]->(@_);
203             }
204              
205             # TWO OR MORE EQUALLY LIKELY CANDIDATES IS AMBIGUOUS...
206             elsif ( @code > 1)
207             {
208             my $handler = $Class::Multimethods::ambiguous_handler{'NAME'};
209             if (defined $handler)
210             {
211             return $handler->(@_)
212             if ref $handler;
213             return $Class::Multimethods::dispatch{'NAME'}{$handler}->(@_)
214             if defined $Class::Multimethods::dispatch{'NAME'}{$handler};
215             }
216             _prettify($sig);
217             croak "Cannot resolve call to multimethod NAME($sig). " .
218             "The multimethods:\n" .
219             join("\n",
220             map { "\tNAME(" . join(',',@$_) . ")" }
221             @candidates) .
222             "\nare equally viable";
223             }
224              
225             # IF *NO* CANDIDATE, NO WAY TO DISPATCH THE CALL
226             else
227             {
228             my $handler = $Class::Multimethods::no_match_handler{'NAME'};
229             if (defined $handler)
230             {
231             return $handler->(@_)
232             if ref $handler;
233             return $Class::Multimethods::dispatch{'NAME'}{$handler}->(@_)
234             if defined $Class::Multimethods::dispatch{'NAME'}{$handler};
235             }
236             _prettify($sig);
237             croak "No viable candidate for call to multimethod NAME($sig)";
238             }
239             }
240             1;
241              
242             };
243 8         69 $code =~ s/PACKAGE/$pkg/g;
244 8         134 $code =~ s/NAME/$name/g;
245 8         6001 return $code;
246             }
247              
248              
249             # THIS SUB TAKES A LIST OF EQUALLY LIKELY CANDIDATES (I.E. THE SAME NUMBER OF
250             # INHERITANCE STEPS AWAY FROM THE ACTUAL ARG TYPES) AND BUILDS A LIST OF
251             # MATCHING ONES. IF THERE AREN'T ANY MATCHES, IT BUILDS A NEW LIST OF
252             # CANDIDATES, BY GENERATING PERMUTATIONS OF THE SET OF PARENT TYPES FOR
253             # EACH ARG TYPE.
254              
255             sub resolve
256             {
257 1130     1130 0 1683 my ($name, $candidates, $matches, $tried) = @_;
258 1130         2020 my %newcandidates = ();
259 1130         1717 foreach my $candidate ( @$candidates )
260             {
261             # print "trying @$candidate...\n";
262              
263             # BUILD THE TYPE SIGNATURE AND ENSURE IT HASN'T ALREADY BEEN CHECKED
264              
265 1683         2795 my $sig = join ',', @$candidate;
266 1683 50       3435 next if $tried->{$sig};
267 1683         8770 $tried->{$sig} = 1;
268            
269             # LOOK FOR A MATCHING SUB REF IN THE DISPATCH TABLE AND REMEMBER IT...
270              
271 1683         2679 my $match = $Class::Multimethods::dispatch{$name}{$sig};
272 1683 100 66     3582 if ($match && ref($match) eq 'CODE')
273             {
274 135         163 push @$matches, $match;
275 135         273 next;
276             }
277              
278             # OTHERWISE, GENERATE A NEW SET OF CANDIDATES BY REPLACING EACH
279             # ARGUMENT TYPE IN TURN BY EACH OF ITS IMMEDIATE PARENTS. EACH SUCH
280             # NEW CANDIDATE MUST BE EXACTLY 1 DERIVATION MORE EXPENSIVE THAN
281             # THE CURRENT GENERATION OF CANDIDATES. NOTE, THAT IF A MATCH HAS
282             # BEEN FOUND AT THE CURRENT GENERATION, THERE IS NO NEED TO LOOK
283             # ANY DEEPER...
284              
285 1548 100       2686 if (!@$matches)
286             {
287 1519         2882 for (my $i = 0; $i<@$candidate ; $i++)
288             {
289 3048 100       7270 next if $candidate->[$i] =~ /[^\w:#]/;
290 1     1   7 no strict 'refs';
  1         2  
  1         303  
291 2895         2641 my @parents;
292 2895 100       8063 if ($candidate->[$i] eq '#')
    50          
    50          
293 2         6 { @parents = ('$') }
294             elsif ($candidate->[$i] =~ /\AClass::Multimethods::SUPERCLASS_IS::(.+)/)
295 0         0 { @parents = ($1) }
296             elsif ($candidate->[$i] =~ /\AClass::Multimethods::SUPERCLASS_OF::(.+)/)
297 0 0       0 { @parents = ($1 eq '#') ? '$' : @{$1."::ISA"} }
  0         0  
298             else
299 2893         2733 { @parents = @{$candidate->[$i]."::ISA"} }
  2893         8144  
300 2895         5904 foreach my $parent ( @parents )
301             {
302 1702         2769 my @newcandidate = @$candidate;
303 1702         2142 $newcandidate[$i] = $parent;
304 1702         11067 $newcandidates{join ',', @newcandidate} = [@newcandidate];
305             }
306             }
307            
308             }
309             }
310              
311             # IF NO MATCHES AT THE CURRENT LEVEL, RESET THE CANDIDATES TO THOSE AT
312             # THE NEXT LEVEL...
313              
314 1130 100       3997 @$candidates = values %newcandidates unless @$matches;
315              
316 1130         35935 return scalar @$matches;
317             }
318              
319             # SUPPORT FOR analyse
320              
321             my %children;
322             my %parents;
323              
324             sub build_relationships
325             {
326 1     1   5 no strict "refs";
  1         2  
  1         150  
327 0     0 0   %children = ( '$' => [ '#' ] );
328 0           %parents = ( '#' => [ '$' ] );
329 0           my (@packages) = @_;
330 0           foreach my $package (@packages)
331             {
332 0           foreach my $parent ( @{$package."::ISA"} )
  0            
333             {
334 0           push @{$children{$parent}}, $package;
  0            
335 0           push @{$parents{$package}}, $parent;
  0            
336             }
337             }
338             }
339              
340              
341             sub list_packages
342             {
343 1     1   5 no strict "refs";
  1         2  
  1         665  
344 0   0 0 0   my $self = $_[0]||"main::";
345 0           my @children = ( $self );
346 0           foreach ( keys %{$self} )
  0            
347             {
348 0 0 0       next unless /::$/ && $_ ne $self;
349 0           push @children, list_packages("$self$_")
350             }
351 0 0         @children = map { s/^main::(.+)$/$1/; s/::$//; $_ } @children
  0            
  0            
  0            
352             unless $_[0];
353 0           return @children;
354             }
355              
356             sub list_ancestors
357             {
358 0     0 0   my ($class) = @_;
359 0           my @ancestors = ();
360 0           foreach my $parent ( @{$parents{$class}} )
  0            
361             {
362 0           push @ancestors, list_ancestors($parent), $parent;
363             }
364 0           return @ancestors;
365             }
366              
367             sub list_descendents
368             {
369 0     0 0   my ($class) = @_;
370 0           my @descendents = ();
371 0           foreach my $child ( @{$children{$class}} )
  0            
372             {
373 0           push @descendents, $child, list_descendents($child);
374             }
375 0           return @descendents;
376             }
377              
378             sub list_hierarchy
379             {
380 0     0 0   my ($class) = @_;
381 0           my @hierarchy = list_ancestors($class);
382 0           push @hierarchy, $class;
383 0           push @hierarchy, list_descendents($class);
384 0           return @hierarchy;
385             }
386              
387             @Class::Multimethods::dont_analyse = qw
388             (
389             Exporter
390             DynaLoader
391             AutoLoader
392             );
393              
394             sub generate_argsets
395             {
396 0     0 0   my ($multimethod) = @_;
397              
398 0           my %ignore;
399 0           @ignore{@Class::Multimethods::dont_analyse} = ();
400              
401              
402 0 0         return unless $min_args{$multimethod};
403              
404 0           my @paramlists = ();
405              
406 0           foreach my $typeset ( keys %{$Class::Multimethods::dispatch{$multimethod}} )
  0            
407             {
408 0 0         next if $typeset =~ /\Q*/;
409 0           my @nexttypes = split /,/, $typeset;
410 0           for my $i (0..$#nexttypes)
411             {
412 0           for my $ancestor ( list_hierarchy $nexttypes[$i] )
413             {
414 0 0         $paramlists[$i]{$ancestor} = 1
415             unless exists $ignore{$ancestor};
416             }
417             }
418             }
419              
420 0           my @argsets = ();
421              
422 0           foreach (@paramlists) { $_ = [keys %{$_}] }
  0            
  0            
423              
424 1     1   1161 use Data::Dumper;
  1         10764  
  1         2245  
425             # print Data::Dumper->Dump([@paramlists]);
426              
427 0           foreach my $argcount ($min_args{$multimethod}..$max_args{$multimethod})
428             {
429 0           push @argsets, combinations(@paramlists[0..$argcount-1]);
430             }
431              
432             # print STDERR Data::Dumper->Dump([@argsets]);
433              
434 0           return @argsets;
435             }
436              
437             sub combinations
438             {
439 0     0 0   my (@paramlists) = @_;
440 0 0         return map { [$_] } @{$paramlists[0]} if (@paramlists==1);
  0            
  0            
441 0           my @combs = ();
442 0           my @subcombs = combinations(@paramlists[1..$#paramlists]);
443 0           foreach my $firstparam (@{$paramlists[0]})
  0            
444             {
445 0           foreach my $subcomb ( @subcombs )
446             {
447 0           push @combs, [$firstparam, @{$subcomb}];
  0            
448             }
449             }
450 0           return @combs;
451             }
452              
453             sub analyse
454             {
455 0     0 0   my ($multimethod, @argsets) = @_;
456 0           my ($package,$file,$line) = caller(0);
457 0   0       my ($sub) = (caller(1))[3] || "main code";
458 0           my $case_count = @argsets;
459 0           my $ambiguous_handler = $ambiguous_handler{$multimethod};
460 0           my $no_match_handler = $no_match_handler{$multimethod};
461 0 0 0       $ambiguous_handler = "$multimethod($ambiguous_handler)"
462             if $ambiguous_handler && ref($ambiguous_handler) ne "CODE";
463 0 0 0       $no_match_handler = "$multimethod($no_match_handler)"
464             if $no_match_handler && ref($no_match_handler) ne "CODE";
465 0           build_relationships list_packages;
466 0 0         if ($case_count)
467             {
468 0           my @newargsets;
469 0           foreach my $argset ( @argsets )
470             {
471 0 0         my @argset = map { ref eq 'ARRAY' ? $_ : [$_] } @$argset;
  0            
472 0           push @newargsets, combinations(@argset);
473             }
474 0           @argsets = @newargsets;
475 0           $case_count = @argsets;
476             }
477             else
478             {
479 0           @argsets = generate_argsets($multimethod);
480 0           $case_count = @argsets;
481 0 0         unless ($case_count)
482             {
483 0           print STDERR "[No variants found for $multimethod. No analysis possible.]\n\n";
484 0           print STDERR "="x72, "\n\n";
485 0           return;
486              
487             }
488 0           print STDERR "[Generated $case_count test cases for $multimethod]\n\n"
489             }
490              
491 0           print STDERR "Analysing calls to $multimethod from $sub ($file, line $line):\n";
492 0           my $case = 1;
493              
494 0           my $successes = 0;
495 0           my @fails = ();
496 0           my @ambigs = ();
497              
498 0           foreach my $argset ( @argsets )
499             {
500 0           my $callsig = "${multimethod}(".join(",",@$argset).")";
501 0           print STDERR "\n\t[$case/$case_count] For call to $callsig:\n\n";
502 0           $case++;
503 0           my @ordered = sort {
504 0           $a->{wrong_length} - $b->{wrong_length}
505             ||
506 0 0 0       @{$a->{incomp}} - @{$b->{incomp}}
  0   0        
507             ||
508             $a->{generic} - $b->{generic}
509             ||
510             $a->{sum_dist} <=> $b->{sum_dist}
511             }
512             evaluate($multimethod, $argset);
513              
514              
515 0 0 0       if ($ordered[0] && !@{$ordered[0]->{incomp}})
  0            
516             {
517 0           my $i;
518 0           for ($i=1; $i<@ordered; $i++)
519             {
520 0 0 0       last if @{$ordered[$i]->{incomp}} ||
  0   0        
      0        
521             $ordered[$i]->{wrong_length} ||
522             $ordered[$i]->{sum_dist} >
523             $ordered[0]->{sum_dist} ||
524             $ordered[$i]->{generic} !=
525             $ordered[0]->{generic};
526             }
527 0           $ordered[$_]->{less_viable} = 1 for ($i..$#ordered);
528 0 0         if ($i>1)
529             {
530 0           $ordered[$i]->{ambig} = 1 while ($i-->0)
531             }
532             }
533              
534 0           my $first = 1;
535 0           my $min_dist = 0;
536 0           push @fails, "\t\t$callsig\n"; # ASSUME THE WORST
537              
538             # CHECK FOR REOLUTION IF DISPATCH FAILS
539              
540 0           my $winner = $ordered[0];
541 0 0 0       if ($winner && $winner->{ambig} && $ambiguous_handler)
    0 0        
      0        
      0        
      0        
542             {
543 0           print STDERR "\t\t(+) $ambiguous_handler\n\t\t\t>>> Ambiguous dispatch handler invoked.\n\n";
544 0           $first = 0;
545 0           $successes++;
546 0           pop @fails;
547             }
548             elsif ($winner
549             && (@{$winner->{incomp}} || $winner->{wrong_length})
550             && $no_match_handler )
551             {
552 0           print STDERR "\t\t(+) $no_match_handler\n\t\t\t>>> Dispatch failure handler invoked.\n\n";
553 0           $first = 0;
554 0           $successes++;
555 0           pop @fails;
556             }
557 0           foreach my $variant (@ordered)
558             {
559 0 0 0       if ($variant->{ambig})
    0          
    0          
    0          
    0          
    0          
    0          
560 0           {
561 0           print STDERR "\t\t(?) $variant->{sig}\n\t\t\t>>> Ambiguous. Distance: $variant->{sum_dist}\n";
562 0 0         push @ambigs, pop @fails if $first;
563             }
564             elsif (@{$variant->{incomp}} == 1)
565 0           {
566 0           print STDERR "\t\t(-) $variant->{sig}\n\t\t\t>>> Not viable. Incompatible argument: ", @{$variant->{incomp}}, "\n";
  0            
567             }
568             elsif (@{$variant->{incomp}})
569             {
570 0           print STDERR "\t\t(-) $variant->{sig}\n\t\t\t>>> Not viable. Incompatible arguments: ", join(",",@{$variant->{incomp}}), "\n";
  0            
571             }
572             elsif ($variant->{wrong_length})
573             {
574 0           print STDERR "\t\t(-) $variant->{sig}\n\t\t\t>>> Not viable. Wrong number of arguments\n";
575             }
576             elsif ($first)
577             {
578 0           print STDERR "\t\t(+) $variant->{sig}\n\t\t\t>>> Target. Distance: $variant->{sum_dist}\n\n";
579 0           $min_dist = $variant->{sum_dist};
580 0           $successes++;
581 0           pop @fails;
582             }
583             elsif ($variant->{generic} && $variant->{sum_dist} < $min_dist)
584             {
585 0           print STDERR "\t\t(*) $variant->{sig}\n\t\t\t>>> Viable, but generic. Distance: $variant->{sum_dist} (generic)\n";
586             }
587             elsif ($variant->{generic})
588             {
589 0           print STDERR "\t\t(*) $variant->{sig}\n\t\t\t>>> Viable. Distance: $variant->{sum_dist} (generic)\n";
590             }
591             else
592             {
593 0           print STDERR "\t\t(x) $variant->{sig}\n\t\t\t>>> Viable. Distance: $variant->{sum_dist}\n";
594             }
595 0           $first = 0;
596             }
597 0           print STDERR "\n";
598             }
599 0           print STDERR "\n", "-"x72, "\nSummary for calls to $multimethod from $sub ($file, line $line):\n\n";
600              
601 0           printf STDERR "\tSuccessful dispatch in %2.0f%% of calls\n",
602             $successes/$case_count*100;
603 0           printf STDERR "\tDispatch ambiguous for %2.0f%% of calls\n",
604             @ambigs/$case_count*100;
605 0           printf STDERR "\tWas unable to dispatch %2.0f%% of calls\n",
606             @fails/$case_count*100;
607              
608 0 0         print STDERR "\nAmbiguous calls:\n", @ambigs if @ambigs;
609 0 0         print STDERR "\nUndispatchable:\n", @fails if @fails;
610              
611 0           print STDERR "\n", "="x72, "\n\n";
612              
613             }
614              
615             my %distance;
616             sub distance
617             {
618 0     0 0   my ($from, $to) = @_;
619              
620 0 0         return 0 if $from eq $to;
621 0 0         return -1 if $to eq '*';
622 0 0         return $distance{$from}{$to} if defined $distance{$from}{$to};
623              
624 0 0         if ($parents{$from})
625             {
626 0           foreach my $parent ( @{$parents{$from}} )
  0            
627             {
628 0           my $distance = distance($parent,$to);
629 0 0         if (defined $distance)
630             {
631 0           $distance{$from}{$to} = $distance+1;
632 0           return $distance+1;
633             }
634             }
635             }
636 0           return undef;
637             }
638              
639             sub evaluate
640             {
641 0     0 0   my ($name, $types) = @_;
642 0           my @results = ();
643 0           my $sig = join ',', @$types;
644              
645 0           SET: foreach my $typeset ( keys %{$Class::Multimethods::dispatch{$name}} )
  0            
646             {
647            
648 0           push @results, { sig => "$name($typeset)",
649             incomp => [],
650             sum_dist => 0,
651             wrong_length => 0,
652             generic => 0,
653             };
654 0           my @nexttypes = split /,/, $typeset;
655 0 0         if (@nexttypes != @$types)
656             {
657 0           $results[-1]->{wrong_length} = 1;
658 0           next SET;
659             }
660              
661 0           my @dist;
662 0           PARAM: for (my $i=0; $i<@$types; $i++)
663             {
664 0           my $nextdist = distance($types->[$i], $nexttypes[$i]);
665 0           push @{$results[-1]->{dist}}, $nextdist;
  0            
666 0 0         if (!defined $nextdist)
    0          
667             {
668 0           push @{$results[-1]->{incomp}}, $i;
  0            
669             }
670             elsif ($nextdist < 0)
671             {
672 0           $results[-1]->{generic} = 1;
673             }
674             else
675             {
676 0           $results[-1]->{sum_dist} += $nextdist
677             }
678             }
679             }
680 0           return @results;
681             }
682              
683              
684             1;
685             __END__