File Coverage

blib/lib/Acme/Signature/Arity.pm
Criterion Covered Total %
statement 49 49 100.0
branch 8 12 66.6
condition 2 3 66.6
subroutine 11 11 100.0
pod 4 4 100.0
total 74 79 93.6


line stmt bran cond sub pod time code
1             package Acme::Signature::Arity;
2             # ABSTRACT: find out how a piece of code expects to be called
3              
4 1     1   74105 use strict;
  1         10  
  1         30  
5 1     1   5 use warnings;
  1         2  
  1         54  
6              
7             our $VERSION = '0.003';
8             our $AUTHORITY;
9              
10 1     1   7 use B;
  1         2  
  1         48  
11 1     1   5 use List::Util qw(min);
  1         2  
  1         105  
12 1     1   7 use experimental qw(signatures);
  1         2  
  1         5  
13              
14 1     1   630 use parent qw(Exporter);
  1         288  
  1         6  
15              
16             =head1 NAME
17              
18             Acme::Signature::Arity - provides reliable, production-ready signature introspection
19              
20             =head1 DESCRIPTION
21              
22             You'll know if you need this.
23              
24             If you're just curious, perhaps start with L.
25              
26             No part of this is expected to work in any way when given a sub that has a prototype.
27             There are other tools for those: L.
28              
29             For subs that don't have a prototype, this is I not expected to work. It might help
30             demonstrate where to look if you wanted to write something proper, though.
31              
32             =cut
33              
34             our @EXPORT_OK = qw(arity min_arity max_arity coderef_ignoring_extra);
35             our @EXPORT = qw(min_arity max_arity);
36              
37             =head1 Exported functions
38              
39             =head2 arity
40              
41             Returns the C details for the first opcode for a coderef CV.
42             If that code uses signatures, this might give you some internal details
43             which mean something about the expected parameters.
44              
45             Expected return information, as a list:
46              
47             =over 4
48              
49             =item * number of required scalar parameters
50              
51             =item * number of optional scalar parameters (probably because there are defaults)
52              
53             =item * a character representing the slurping behaviour, might be '@' or '%', or nothing (undef?) if it's
54             just a fixed list of scalar parameters
55              
56             =back
57              
58             This can also throw exceptions. That should only happen if you give it something that isn't
59             a coderef, or if internals change enough that the entirely-unjustified assumptions made by
60             this module are somehow no longer valid. Maybe they never were in the first place.
61              
62             =cut
63              
64 17     17 1 21 sub arity ($code) {
  17         25  
  17         21  
65 17 50       54 die 'only works on coderefs' unless ref($code) eq 'CODE';
66 17         57 my $cv = B::svref_2object($code);
67 17 50       75 die 'probably not a coderef' unless $cv->isa('B::CV');
68 17         93 my $next = $cv->START->next;
69             # we pretend sub { } is sub (@) { }, for convenience
70 17 100 66     114 return (0, 0, '@') unless $next and $next->isa('B::UNOP_AUX');
71 15         76 return $next->aux_list($cv);
72             }
73              
74             =head2 max_arity
75              
76             Takes a coderef, returns a number or C.
77              
78             If the code uses signatures, this tells you how many parameters you could
79             pass when calling before it complains - C means unlimited.
80              
81             Should also work when there are no signatures, just gives C again.
82              
83             =cut
84              
85 8     8 1 4969 sub max_arity ($code) {
  8         14  
  8         11  
86 8         17 my ($scalars, $optional, $slurp) = arity($code);
87 8 100       27 return undef if $slurp;
88 3         8 return $scalars
89             }
90              
91             =head2 min_arity
92              
93             Takes a coderef, returns a number or C.
94              
95             If the code uses signatures, this tells you how many parameters you need to
96             pass when calling - 0 means that no parameters are required.
97              
98             Should also work when there are no signatures, returning 0 in that case.
99              
100             =cut
101              
102 8     8 1 21031 sub min_arity ($code) {
  8         16  
  8         13  
103 8         19 my ($scalars, $optional, $slurp) = arity($code);
104 8         25 return $scalars - $optional;
105             }
106              
107             =head2 coderef_ignoring_extra
108              
109             Given a coderef, returns a coderef (either the original or wrapped)
110             which won't complain if you try to pass more parameters than it was expecting.
111              
112             This is intended for library authors in situations like this:
113              
114             $useful_library->each(sub ($item) { say "item here: $item" });
115              
116             where you later want to add optional new parameters, and don't trust your users
117             to include the mandatory C<< , @ >> signature definition that indicates excess
118             parameters can be dropped.
119              
120             Usage - let's say your first library version looked like this:
121              
122             sub each ($self, $callback) {
123             my $code = $callback;
124             for my $item ($self->{items}->@*) {
125             $code->($item);
126             }
127             }
128              
129             and you later want to pass the index as an extra parameter, without breaking existing code
130             that assumed there would only ever be one callback parameter...
131              
132             sub each ($self, $callback) {
133             my $code = coderef_ignoring_extra($callback);
134             for my $idx (0..$#{$self->{items}}) {
135             $code->($self->{items}{$idx}, $idx);
136             }
137             }
138              
139             Your library is now at least somewhat backwards-compatible, without sacrificing too
140             many signature-related arity checking features: code expecting the new version
141             will still complain if required parameters are not provided.
142              
143             =cut
144              
145 1     1 1 1902 sub coderef_ignoring_extra ($code) {
  1         2  
  1         2  
146 1         3 my ($scalars, $optional, $slurp) = arity($code);
147             # If we're accepting unlimited parameters, no need to do any more work
148 1 50       8 return $code if $slurp;
149              
150 1         3 my $max_index = $scalars - 1;
151 1     1   1 return sub (@args) {
  1         2  
  1         3  
152             # Some parameters may be optional, so we allow shorter lists as well
153 1 50       10 $code->(@args ? @args[0 .. min($#args, $max_index)] : ());
154             }
155 1         8 }
156              
157             1;
158              
159             __END__