File Coverage

blib/lib/Role/Declare.pm
Criterion Covered Total %
statement 173 174 99.4
branch 24 30 80.0
condition 16 20 80.0
subroutine 49 49 100.0
pod 0 13 0.0
total 262 286 91.6


line stmt bran cond sub pod time code
1             package Role::Declare;
2 6     6   518209 use strict;
  6         46  
  6         193  
3 6     6   34 use warnings;
  6         11  
  6         280  
4             our $VERSION = 0.07;
5              
6 6     6   3769 use Attribute::Handlers;
  6         31899  
  6         35  
7 6     6   242 use Carp qw[ croak ];
  6         12  
  6         394  
8 6     6   4419 use Function::Parameters;
  6         22847  
  6         32  
9 6     6   5062 use Import::Into;
  6         11724  
  6         217  
10 6     6   3989 use Role::Tiny;
  6         27731  
  6         42  
11 6     6   1175 use Scalar::Util qw[ refaddr ];
  6         22  
  6         348  
12 6     6   4311 use Types::Standard ':all';
  6         667645  
  6         101  
13              
14 6     6   326845 use namespace::clean;
  6         88530  
  6         42  
15              
16             use constant { # Attribute::Handlers argument positions
17 6         2761 PACKAGE => 0,
18             SYMBOL => 1,
19             REFERENT => 2,
20             ATTRIBUTE => 3,
21             DATA => 4,
22 6     6   10731 };
  6         22  
23              
24             my %return_hooks;
25              
26             sub _install_hook {
27 23     23   56 my ($type, $target, $hook) = @_;
28 23   100     175 my $hooks = $return_hooks{ refaddr($target) } //= {};
29 23 50       71 croak "A $type hook for $target already exists" if defined $hooks->{$type};
30 23         52 $hooks->{$type} = $hook;
31 23         43 return;
32             }
33              
34 18     18   47 sub _install_scalar_hook { return _install_hook('scalar', @_) }
35 5     5   13 sub _install_list_hook { return _install_hook('list', @_) }
36              
37              
38             sub Return : ATTR(CODE,BEGIN) {
39 15     15 0 6704 my ($referent, $data) = @_[ REFERENT, DATA ];
40              
41 15 50       58 croak 'Only a single constraint is supported' if @$data != 1;
42 15         31 my $constraint = $data->[0];
43            
44             _install_scalar_hook($referent, sub {
45 24     24   47 my $orig = shift;
46 24         69 return $constraint->assert_return(scalar &$orig);
47 15         83 });
48              
49 15         38 return;
50 6     6   52 }
  6         15  
  6         60  
51              
52             sub ReturnMaybe : ATTR(CODE,BEGIN) {
53 2     2 0 2322 $_[DATA][0] = Maybe[ $_[DATA][0] ];
54 2         862 goto &Return;
55 6     6   7706 }
  6         22  
  6         26  
56              
57             sub _make_list_check {
58 5     5   19 my ($constraint, %args) = @_;
59 5         9 my $allow_empty = $args{allow_empty};
60 5 50       19 croak 'List constraint not defined' if not $constraint;
61              
62             return sub {
63 11     11   24 my $orig = shift;
64 11         30 my $retval = [&$orig];
65 11 100 66     86 return if not @$retval and $allow_empty;
66 10         11 return @{ $constraint->assert_return($retval) };
  10         32  
67 5         68 };
68             }
69              
70             sub ReturnList : ATTR(CODE,BEGIN) {
71 2     2 0 1002 my ($referent, $data) = @_[ REFERENT, DATA ];
72 2         10 my $type = ArrayRef($data);
73 2         4183 _install_list_hook($referent, _make_list_check($type, allow_empty => 0));
74 2         5 return;
75 6     6   9096 }
  6         28  
  6         35  
76              
77             sub ReturnMaybeList : ATTR(CODE,BEGIN) {
78 1     1 0 473 my ($referent, $data) = @_[ REFERENT, DATA ];
79 1         4 my $type = ArrayRef($data);
80 1         196 _install_list_hook($referent, _make_list_check($type, allow_empty => 1));
81 1         2 return;
82 6     6   8670 }
  6         13  
  6         67  
83              
84             sub ReturnTuple : ATTR(CODE,BEGIN) {
85 1     1 0 479 my ($referent, $data) = @_[ REFERENT, DATA ];
86              
87 1         4 my $type = Tuple($data);
88 1         7751 _install_list_hook($referent, _make_list_check($type, allow_empty => 0));
89 1         2 return;
90 6     6   7293 }
  6         14  
  6         53  
91              
92             sub ReturnCycleTuple : ATTR(CODE,BEGIN) {
93 1     1 0 4 my ($referent, $data) = @_[ REFERENT, DATA ];
94              
95 1         5 my $type = CycleTuple($data);
96 1         17756 _install_list_hook($referent, _make_list_check($type, allow_empty => 0));
97 1         3 return;
98 6     6   7121 }
  6         16  
  6         46  
99              
100             sub ReturnHash : ATTR(CODE,BEGIN) {
101 1     1 0 829 my $data = $_[DATA];
102 1 50       7 croak 'Only a single constraint is supported' if @$data != 1;
103 1         3 unshift @$data, Str;
104 1         7 goto &ReturnCycleTuple;
105 6     6   8594 }
  6         21  
  6         36  
106              
107             sub _make_self_check {
108 3     3   10 my %args = @_;
109 3         8 my $allow_undef = $args{undef_ok};
110             return sub {
111 8     8   15 my $orig = shift;
112 8         29 my $orig_self_addr = refaddr($_[0]);
113 8         40 my $self = &$orig;
114 7 100 66     73 return $self if not defined $self and $allow_undef;
115 6 100 100     41 return $self if ref $self and refaddr($self) eq $orig_self_addr;
116 3         60 croak "$self was not the original invocant";
117 3         21 };
118             }
119              
120             sub ReturnSelf : ATTR(CODE,BEGIN) {
121 1     1 0 402 my $referent = $_[REFERENT];
122 1         5 _install_scalar_hook($referent, _make_self_check(undef_ok => 0));
123 1         2 return;
124 6     6   8331 }
  6         16  
  6         34  
125              
126             sub ReturnMaybeSelf : ATTR(CODE,BEGIN) {
127 2     2 0 912 my $referent = $_[REFERENT];
128 2         6 _install_scalar_hook($referent, _make_self_check(undef_ok => 1));
129 2         5 return;
130 6     6   6842 }
  6         14  
  6         36  
131              
132             sub ReturnObject : ATTR(CODE,BEGIN) {
133 1     1 0 415 $_[DATA][0] = Object;
134 1         9 goto &Return;
135 6     6   6884 }
  6         16  
  6         86  
136              
137             sub ReturnMaybeObject : ATTR(CODE,BEGIN) {
138 2     2 0 898 $_[DATA][0] = Maybe[Object];
139 2         880 goto &Return;
140 6     6   7154 }
  6         14  
  6         67  
141              
142             sub ReturnInstanceOf : ATTR(CODE,BEGIN) {
143 1     1 0 758 $_[DATA][0] = InstanceOf[$_[DATA][0]];
144 1         5212 goto &Return;
145 6     6   7108 }
  6         14  
  6         23  
146              
147             sub ReturnMaybeInstanceOf : ATTR(CODE,BEGIN) {
148 2     2 0 1511 $_[DATA][0] = Maybe[InstanceOf[$_[DATA][0]]];
149 2         1002 goto &Return;
150 6     6   6695 }
  6         15  
  6         34  
151              
152             sub _build_validator {
153 19     19   33 my ($hooks) = @_;
154 19         33 my $val_scalar = $hooks->{scalar};
155 19         33 my $val_list = $hooks->{list};
156             return sub {
157 44 100 66 44   61635 goto &$val_list if wantarray and $val_list;
158 33 100 66     208 goto &$val_scalar if defined wantarray and $val_scalar;
159              
160             # void context or no validators
161 1         2 my $orig = shift;
162 1         5 goto &$orig;
163 19         93 };
164             }
165              
166             sub import {
167 6     6   58 my $class = shift;
168 6         32 my $package = scalar caller;
169 6 50       168 return if $class ne __PACKAGE__; # don't let this import spread around
170              
171 6         15 my ($lax, $skip_typecheck);
172 6         74 foreach my $mode (@_) {
173 4 100       15 if ($mode eq '-lax') {
    50          
174 2         6 $lax = 1;
175             }
176             elsif ($mode eq '-no_type_check') {
177 2         4 $skip_typecheck = 1;
178             }
179             else {
180 0         0 croak "Unsupported mode: $mode";
181             }
182             }
183              
184             # make the caller a role first, so we can install modifiers
185 6         80 Role::Tiny->import::into($package);
186 6         2793 my $before = $package->can('before');
187 6         26 my $around = $package->can('around');
188              
189             my $installer = sub {
190 23     23   4862 my ($name, $coderef) = @_;
191 23         91 $before->($name, $coderef);
192              
193 23         265 my $hooks = delete $return_hooks{ refaddr($coderef) };
194 23 100 100     127 if (defined $hooks and not $skip_typecheck) {
195 19         45 my $return_validator = _build_validator($hooks);
196 19         77 $around->($name, $return_validator);
197             }
198              
199 23         1412 return;
200 6         32 };
201              
202 6         33 my %common_args = (
203             name => 'required',
204             install_sub => $installer,
205             );
206 6 100       35 $common_args{check_argument_count} = 0 if $lax;
207 6 100       34 $common_args{check_argument_types} = 0 if $skip_typecheck;
208 6         49 Function::Parameters->import(
209             {
210             class_method => {
211             %common_args,
212             shift => [ [ '$class', ClassName ] ],
213             },
214             },
215             {
216             instance_method => {
217             %common_args,
218             shift => [ [ '$self', Object ] ],
219             },
220             },
221             {
222             method => {
223             %common_args,
224             shift => [ '$self' ],
225             },
226             },
227             );
228              
229             # allow importing package to use our attributes
230 6         4746 parent->import::into($package, $class);
231              
232 6         6957 return;
233             }
234              
235             1;