File Coverage

blib/lib/Kavorka/Multi.pm
Criterion Covered Total %
statement 174 181 96.1
branch 70 90 77.7
condition 12 20 60.0
subroutine 29 32 90.6
pod n/a
total 285 323 88.2


line stmt bran cond sub pod time code
1 6     6   2503 use 5.014;
  6         13  
2 6     6   22 use strict;
  6         6  
  6         117  
3 6     6   20 use warnings;
  6         5  
  6         143  
4              
5 6     6   23 use Sub::Util ();
  6         5  
  6         273  
6              
7             package Kavorka::Multi;
8              
9             our $AUTHORITY = 'cpan:TOBYINK';
10             our $VERSION = '0.037';
11              
12 6     6   22 use Parse::Keyword {};
  6         5  
  6         33  
13 6     6   1788 use Parse::KeywordX;
  6         11  
  6         63  
14              
15 6     6   3846 use Moo;
  6         32289  
  6         27  
16             with 'Kavorka::Sub';
17 6     6   9842 use namespace::sweep;
  6         61922  
  6         35  
18              
19             has multi_type => (is => 'ro', required => 1);
20             has declared_long_name => (is => 'rwp');
21             has qualified_long_name => (is => 'rwp');
22              
23             around parse => sub
24             {
25             my $next = shift;
26             my $class = shift;
27            
28             lex_read_space;
29             my $type = parse_name('keyword', 0);
30             lex_read_space;
31            
32             $class->multi_parse($next, $type, @_);
33             };
34              
35             sub multi_parse
36             {
37 13     13   13 my $class = shift;
38 13         27 my ($parse_method, $keyword, @args) = @_;
39            
40 13         11 my $implementation;
41 13 50       157 if ($^H{Kavorka} =~ /\b$keyword=(\S+)/)
42             {
43 13         28 $implementation = $1;
44             }
45             else
46             {
47 0         0 Carp::croak("Could not resolve keyword '$keyword'");
48             }
49            
50 13         44 return $class->$parse_method(@args, multi_type => $implementation);
51             }
52              
53             after parse_attributes => sub
54             {
55             my $self = shift;
56            
57             my @attr = @{$self->attributes};
58            
59             my @filtered;
60              
61             $_->[0] eq 'long'
62             ? ($self->_set_declared_long_name($_->[1]), $self->_set_qualified_long_name(scalar Kavorka::_fqname $_->[1]))
63             : push(@filtered, $_)
64             for @attr;
65            
66             @{$self->attributes} = @filtered;
67             };
68              
69             after parse_signature => sub
70             {
71             my $self = shift;
72             my $sig = $self->signature;
73            
74             for my $param (@{$sig->params})
75             {
76             Carp::croak("Type constraints for parameters cannot be 'assumed' in a multi sub")
77             if $param->traits->{assumed};
78             }
79            
80             $self->signature->_set_nobble_checks(1);
81             };
82              
83 0     0   0 sub allow_anonymous { 0 }
84 0     0   0 sub allow_lexical { 0 }
85              
86             sub default_attributes
87             {
88 13     13   311 my $code = $_[0]->multi_type->can('default_attributes');
89 13         31 goto $code;
90             }
91              
92             sub default_invocant
93             {
94 13     13   96 my $code = $_[0]->multi_type->can('default_invocant');
95 13         43 goto $code;
96             }
97              
98             sub forward_declare
99             {
100 0     0   0 my $code = $_[0]->multi_type->can('forward_declare');
101 0         0 goto $code;
102             }
103              
104             sub invocation_style
105             {
106 22 50   22   103 $_[0]->multi_type->invocation_style
107             or Carp::croak("No invocation style defined");
108             }
109              
110             our %DISPATCH_TABLE;
111             our %DISPATCH_STYLE;
112             our %INVALIDATION;
113              
114             sub __gather_candidates
115             {
116 14     14   19 my ($pkg, $subname, $args) = @_;
117            
118 14 100       43 if ($DISPATCH_STYLE{$pkg}{$subname} eq 'fun')
119             {
120 3         4 return @{$DISPATCH_TABLE{$pkg}{$subname}};
  3         11  
121             }
122            
123 11         46 require mro;
124 11   33     32 my $invocant = ref($args->[0]) || $args->[0];
125 11 100       12 return map @{$DISPATCH_TABLE{$_}{$subname} || [] }, @{ $invocant->mro::get_linear_isa };
  26         80  
  11         46  
126             }
127              
128             sub __dispatch
129             {
130 6     6   5 my ($pkg, $subname) = @{ +shift };
  6         9  
131            
132 6         12 for my $c ( __gather_candidates($pkg, $subname, \@_) )
133             {
134 7         2640 my @copy = @_;
135 7 100       31 next unless $c->signature->check(@copy);
136 6         12 my $body = $c->body;
137 6         17 goto $body;
138             }
139            
140 0         0 Carp::croak("Arguments to $pkg\::$subname did not match any known signature for multi sub");
141             }
142              
143             sub __compile
144             {
145 8     8   15 my ($pkg, $subname) = @_;
146            
147 8         27 my @candidates = __gather_candidates($pkg, $subname, [$pkg]);
148 8         37 my @coderefs = map $_->body, @candidates;
149            
150 8         13 my $slowpath = '';
151 8 100       32 if ($DISPATCH_STYLE{$pkg}{$subname} ne 'fun')
152             {
153 5         19 my $this = [$pkg, $subname];
154 5   100     6 push @{ $INVALIDATION{"$_\::$subname"} ||= [] }, $this for @{ $pkg->mro::get_linear_isa };
  5         19  
  8         55  
155            
156 5         53 $slowpath = sprintf(
157             'if ((ref($_[0]) || $_[0]) ne %s) { unshift @_, [%s, %s]; goto \\&Kavorka::Multi::__dispatch }',
158             B::perlstring($pkg),
159             B::perlstring($pkg),
160             B::perlstring($subname),
161             );
162             }
163            
164             my $compiled = join q[] => (
165             map {
166 8         21 my $sig = $candidates[$_]->signature;
  17         42  
167 17 0 33     125 $sig && $sig->nobble_checks ? sprintf(
    50          
168             "\@tmp = \@_; if (%s) { unshift \@_, \$Kavorka::Signature::NOBBLE; goto \$coderefs[%d] }\n",
169             $candidates[$_]->signature->inline_check('@tmp'),
170             $_,
171             ) :
172             $sig ? sprintf(
173             "\@tmp = \@_; if (%s) { goto \$coderefs[%d] }\n",
174             $candidates[$_]->signature->inline_check('@tmp'),
175             $_,
176             ) : sprintf('goto \$coderefs[%d];', $_);
177             } 0 .. $#candidates,
178             );
179            
180 8         23 my $error = "Carp::croak(qq/Arguments to $pkg\::$subname did not match any known signature for multi sub/);";
181            
182 8 100 66 7   1785 Sub::Util::set_subname(
  13 100 66 3   36  
  13 50 66 3   22  
  13 100 66 3   730  
  10 50   3   27  
  10 100   1   18  
  10 100       348  
  10 100       40  
  7 100       260  
  7 100       536  
  7 100       19  
  7 100       15  
  9 50       3706  
  9 100       20  
  9 100       16  
  9 100       358  
  7 100       21  
  6 50       10  
  6 100       131  
  5 50       12  
  5 100       5  
  5 50       112  
  3 50       5  
  3 50       8  
  3 100       5  
  3 50       11  
  3 100       381  
  5 50       826  
  2 50       3  
  2 50       6  
  5 50       6  
  5 50       29  
  5 50       8  
  5         13  
  4         4  
  4         12  
  4         5  
  4         11  
  4         6  
  4         8  
  3         13  
  1         1  
  1         3  
  1         2  
  1         3  
  2         380  
  2         3  
  2         5  
  2         3  
  2         4  
  2         5  
  2         3  
  2         5  
  2         1  
  2         7  
  1         1  
  1         3  
  1         2  
  1         3  
  1         99  
  1         2  
  1         2  
  1         1  
  1         3  
  1         3  
  1         7  
  1         2  
  1         1  
  1         2  
  1         2  
  1         2  
  1         2  
  1         3  
  0            
183             "$pkg\::$subname",
184             eval("package $pkg; sub { $slowpath; my \@tmp; $compiled; $error }"),
185             );
186             }
187              
188             sub __defer_compile
189             {
190 16     16   473 my ($pkg, $subname) = @_;
191             return Sub::Util::set_subname(
192             "$pkg\::$subname" => sub {
193 6     6   6919 no strict "refs";
  6         12  
  6         205  
194 6     6   19 no warnings "redefine";
  6         10  
  6         1147  
195 10     23   888 *{"$pkg\::$subname"} = (my $compiled = __compile($pkg, $subname));
  10         36  
196 10         201 goto $compiled;
197             },
198 16         126 );
199             }
200              
201             sub install_sub
202             {
203 15     19   21 my $self = shift;
204 15         82 my ($pkg, $subname) = ($self->qualified_name =~ /^(.+)::(\w+)$/);
205            
206 15 100       53 unless ($DISPATCH_TABLE{$pkg}{$subname})
207             {
208 11         16 $DISPATCH_TABLE{$pkg}{$subname} = [];
209 11         35 $DISPATCH_STYLE{$pkg}{$subname} = $self->invocation_style;
210             }
211            
212 14 100       32 $DISPATCH_STYLE{$pkg}{$subname} eq $self->invocation_style
213             or Carp::croak("Two different invocation styles used for $subname");
214            
215             {
216             # A placeholder dispatcher that will replace itself with a more
217             # efficient optimized (compiled) dispatcher.
218 6     6   28 no strict "refs";
  6         7  
  6         152  
  14         18  
219 6     6   19 no warnings "redefine";
  6         12  
  6         746  
220 14         30 *{"$pkg\::$subname"} = __defer_compile($pkg, $subname);
  14         106  
221            
222             # Invalidate previously optimized dispatchers in subclasses of $pkg
223 10         40 *{join '::', @$_} = __defer_compile(@$_)
224 14 100       250 for @{ delete($INVALIDATION{"$pkg\::$subname"}) || [] };
  28         5879  
225             }
226            
227 22         47 my $long = $self->qualified_long_name;
228 22 100       53 if (defined $long)
229             {
230 6     6   23 no strict 'refs';
  6         9  
  6         469  
231 10         17 *$long = $self->body;
232             }
233            
234 22         26 push @{ $DISPATCH_TABLE{$pkg}{$subname} }, $self;
  22         75  
235             }
236              
237             1;