File Coverage

blib/lib/Mouse/Util/TypeConstraints.pm
Criterion Covered Total %
statement 151 166 90.9
branch 70 84 83.3
condition 31 44 70.4
subroutine 36 38 94.7
pod 14 22 63.6
total 302 354 85.3


line stmt bran cond sub pod time code
1             package Mouse::Util::TypeConstraints;
2 283     283   135391 use Mouse::Util; # enables strict and warnings
  283         296  
  283         1248  
3              
4 283     283   3902 use Mouse::Meta::TypeConstraint;
  283         309  
  283         4337  
5 283     283   844 use Mouse::Exporter;
  283         272  
  283         1123  
6              
7 283     283   960 use Carp ();
  283         305  
  283         3255  
8 283     283   786 use Scalar::Util ();
  283         303  
  283         658070  
9              
10             Mouse::Exporter->setup_import_methods(
11             as_is => [qw(
12             as where message optimize_as
13             from via
14              
15             type subtype class_type role_type maybe_type duck_type
16             enum
17             coerce
18              
19             find_type_constraint
20             register_type_constraint
21             )],
22             );
23              
24             our @CARP_NOT = qw(Mouse::Meta::Attribute);
25              
26             my %TYPE;
27              
28             # The root type
29             $TYPE{Any} = Mouse::Meta::TypeConstraint->new(
30             name => 'Any',
31             );
32              
33             my @builtins = (
34             # $name => $parent, $code,
35              
36             # the base type
37             Item => 'Any', undef,
38              
39             # the maybe[] type
40             Maybe => 'Item', undef,
41              
42             # value types
43             Undef => 'Item', \&Undef,
44             Defined => 'Item', \&Defined,
45             Bool => 'Item', \&Bool,
46             Value => 'Defined', \&Value,
47             Str => 'Value', \&Str,
48             Num => 'Str', \&Num,
49             Int => 'Num', \&Int,
50              
51             # ref types
52             Ref => 'Defined', \&Ref,
53             ScalarRef => 'Ref', \&ScalarRef,
54             ArrayRef => 'Ref', \&ArrayRef,
55             HashRef => 'Ref', \&HashRef,
56             CodeRef => 'Ref', \&CodeRef,
57             RegexpRef => 'Ref', \&RegexpRef,
58             GlobRef => 'Ref', \&GlobRef,
59              
60             # object types
61             FileHandle => 'GlobRef', \&FileHandle,
62             Object => 'Ref', \&Object,
63              
64             # special string types
65             ClassName => 'Str', \&ClassName,
66             RoleName => 'ClassName', \&RoleName,
67             );
68              
69             while (my ($name, $parent, $code) = splice @builtins, 0, 3) {
70             $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
71             name => $name,
72             parent => $TYPE{$parent},
73             optimized => $code,
74             );
75             }
76              
77             # parametarizable types
78             $TYPE{Maybe} {constraint_generator} = \&_parameterize_Maybe_for;
79             $TYPE{ArrayRef}{constraint_generator} = \&_parameterize_ArrayRef_for;
80             $TYPE{HashRef} {constraint_generator} = \&_parameterize_HashRef_for;
81              
82             # sugars
83 59     59 1 12473 sub as ($) { (as => $_[0]) } ## no critic
84 58     58 1 3184 sub where (&) { (where => $_[0]) } ## no critic
85 7     7 0 18 sub message (&) { (message => $_[0]) } ## no critic
86 0     0 0 0 sub optimize_as (&) { (optimize_as => $_[0]) } ## no critic
87              
88 37     37 1 108 sub from { @_ }
89 37     37 1 2307 sub via (&) { $_[0] } ## no critic
90              
91             # type utilities
92              
93             sub optimized_constraints { # DEPRECATED
94 0     0 0 0 Carp::cluck('optimized_constraints() has been deprecated');
95 0         0 return \%TYPE;
96             }
97              
98             undef @builtins; # free the allocated memory
99             @builtins = keys %TYPE; # reuse it
100 1     1 1 7 sub list_all_builtin_type_constraints { @builtins }
101 5     5 1 469 sub list_all_type_constraints { keys %TYPE }
102              
103             sub _define_type {
104 853     853   870 my $is_subtype = shift;
105 853         741 my $name;
106             my %args;
107              
108 853 50 33     4512 if(@_ == 1 && ref $_[0] ){ # @_ : { name => $name, where => ... }
    50 66        
    100          
109 0         0 %args = %{$_[0]};
  0         0  
110             }
111             elsif(@_ == 2 && ref $_[1]) { # @_ : $name => { where => ... }
112 0         0 $name = $_[0];
113 0         0 %args = %{$_[1]};
  0         0  
114             }
115             elsif(@_ % 2) { # @_ : $name => ( where => ... )
116 850         2668 ($name, %args) = @_;
117             }
118             else{ # @_ : (name => $name, where => ...)
119 3         10 %args = @_;
120             }
121              
122 853 100       1791 if(!defined $name){
123 7         16 $name = $args{name};
124             }
125              
126 853         1395 $args{name} = $name;
127              
128 853         1139 my $parent = delete $args{as};
129 853 100 100     3343 if($is_subtype && !$parent){
130 3         7 $parent = delete $args{name};
131 3         7 $name = undef;
132             }
133              
134 853 100       1471 if(defined $parent) {
135 838         1408 $args{parent} = find_or_create_isa_type_constraint($parent);
136             }
137              
138 852 100       1426 if(defined $name){
139             # set 'package_defined_in' only if it is not a core package
140 842         823 my $this = $args{package_defined_in};
141 842 50       1364 if(!$this){
142 842         1326 $this = caller(1);
143 842 100       6177 if($this !~ /\A Mouse \b/xms){
144 79         142 $args{package_defined_in} = $this;
145             }
146             }
147              
148 842 100       1677 if(defined $TYPE{$name}){
149 10   100     104 my $that = $TYPE{$name}->{package_defined_in} || __PACKAGE__;
150 10 100       31 if($this ne $that) {
151 3         6 my $note = '';
152 3 100       9 if($that eq __PACKAGE__) {
153             $note = sprintf " ('%s' is %s type constraint)",
154             $name,
155 1 50       3 scalar(grep { $name eq $_ } list_all_builtin_type_constraints())
  21         27  
156             ? 'a builtin'
157             : 'an implicitly created';
158             }
159 3         489 Carp::croak("The type constraint '$name' has already been created in $that"
160             . " and cannot be created again in $this" . $note);
161             }
162             }
163             }
164              
165 849 100       1424 $args{constraint} = delete $args{where} if exists $args{where};
166 849 100       1850 $args{optimized} = delete $args{optimized_as} if exists $args{optimized_as};
167              
168 849         3590 my $constraint = Mouse::Meta::TypeConstraint->new(%args);
169              
170 849 100       1561 if(defined $name){
171 839         2727 return $TYPE{$name} = $constraint;
172             }
173             else{
174 10         43 return $constraint;
175             }
176             }
177              
178             sub type {
179 15     15 1 38 return _define_type 0, @_;
180             }
181              
182             sub subtype {
183 825     825 1 1442 return _define_type 1, @_;
184             }
185              
186             sub coerce { # coerce $type, from $from, via { ... }, ...
187 31     31 1 43 my $type_name = shift;
188 31 100       78 my $type = find_type_constraint($type_name)
189             or Carp::croak("Cannot find type '$type_name', perhaps you forgot to load it");
190              
191 30         146 $type->_add_type_coercions(@_);
192 28         45 return;
193             }
194              
195             sub class_type {
196 570     570 1 691 my($name, $options) = @_;
197 570   33     1894 my $class = $options->{class} || $name;
198              
199             # ClassType
200 570         3441 return subtype $name => (
201             as => 'Object',
202             optimized_as => Mouse::Util::generate_isa_predicate_for($class),
203             class => $class,
204             );
205             }
206              
207             sub role_type {
208 193     193 1 278 my($name, $options) = @_;
209 193   66     649 my $role = $options->{role} || $name;
210              
211             # RoleType
212             return subtype $name => (
213             as => 'Object',
214             optimized_as => sub {
215 23   100 23   4156 return Scalar::Util::blessed($_[0])
216             && Mouse::Util::does_role($_[0], $role);
217             },
218 193         746 role => $role,
219             );
220             }
221              
222             sub maybe_type {
223 1     1 0 1 my $param = shift;
224 1         7 return _find_or_create_parameterized_type($TYPE{Maybe}, $param);
225             }
226              
227             sub duck_type {
228 6     6 1 27 my($name, @methods);
229              
230 6 100       17 if(ref($_[0]) ne 'ARRAY'){
231 4         7 $name = shift;
232             }
233              
234 6 100 66     29 @methods = (@_ == 1 && ref($_[0]) eq 'ARRAY') ? @{$_[0]} : @_;
  4         8  
235              
236             # DuckType
237             return _define_type 1, $name => (
238             as => 'Object',
239             optimized_as => Mouse::Util::generate_can_predicate_for(\@methods),
240             message => sub {
241 1     1   2 my($object) = @_;
242 1         2 my @missing = grep { !$object->can($_) } @methods;
  1         10  
243 1         6 return ref($object)
244             . ' is missing methods '
245             . Mouse::Util::quoted_english_list(@missing);
246             },
247 6         64 methods => \@methods,
248             );
249             }
250              
251             sub enum {
252 7     7 1 872 my($name, %valid);
253              
254 7 100 66     27 if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){
255 5         8 $name = shift;
256             }
257              
258 78         92 %valid = map{ $_ => undef }
259 7 100 66     26 (@_ == 1 && ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_);
  3         6  
260              
261             # EnumType
262             return _define_type 1, $name => (
263             as => 'Str',
264             optimized_as => sub{
265 120   66 120   27799 return defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]};
266             },
267 7         32 );
268             }
269              
270             sub _find_or_create_regular_type{
271 128     128   241 my($spec, $create) = @_;
272              
273 128 100       446 return $TYPE{$spec} if exists $TYPE{$spec};
274              
275 13         48 my $meta = Mouse::Util::get_metaclass_by_name($spec);
276              
277 13 50       38 if(!defined $meta){
278 13 100       45 return $create ? class_type($spec) : undef;
279             }
280              
281 0 0       0 if(Mouse::Util::is_a_metarole($meta)){
282 0         0 return role_type($spec);
283             }
284             else{
285 0         0 return class_type($spec);
286             }
287             }
288              
289             sub _find_or_create_parameterized_type{
290 45     45   55 my($base, $param) = @_;
291              
292 45         330 my $name = sprintf '%s[%s]', $base->name, $param->name;
293              
294 45   100     322 $TYPE{$name} ||= $base->parameterize($param, $name);
295             }
296              
297             sub _find_or_create_union_type{
298 22 50   22   35 return if grep{ not defined } @_; # all things must be defined
  48         92  
299             my @types = sort
300 22 100       30 map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
  48         290  
  2         4  
301              
302 22         55 my $name = join '|', @types;
303              
304             # UnionType
305 22   66     137 $TYPE{$name} ||= Mouse::Meta::TypeConstraint->new(
306             name => $name,
307             type_constraints => \@types,
308             );
309             }
310              
311             # The type parser
312              
313             # param : '[' type ']' | NOTHING
314             sub _parse_param {
315 128     128   200 my($c) = @_;
316              
317 128 100       499 if($c->{spec} =~ s/^\[//){
318 44         111 my $type = _parse_type($c, 1);
319              
320 44 50       185 if($c->{spec} =~ s/^\]//){
321 44         64 return $type;
322             }
323 0         0 Carp::croak("Syntax error in type: missing right square bracket in '$c->{orig}'");
324             }
325              
326 84         114 return undef;
327             }
328              
329             # name : [\w.:]+
330             sub _parse_name {
331 128     128   119 my($c, $create) = @_;
332              
333 128 50       604 if($c->{spec} =~ s/\A ([\w.:]+) //xms){
334 128         250 return _find_or_create_regular_type($1, $create);
335             }
336 0         0 Carp::croak("Syntax error in type: expect type name near '$c->{spec}' in '$c->{orig}'");
337             }
338              
339             # single_type : name param
340             sub _parse_single_type {
341 128     128   143 my($c, $create) = @_;
342              
343 128         193 my $type = _parse_name($c, $create);
344 128         271 my $param = _parse_param($c);
345              
346 128 100       232 if(defined $type){
    50          
347 118 100       164 if(defined $param){
348 44         116 return _find_or_create_parameterized_type($type, $param);
349             }
350             else {
351 74         133 return $type;
352             }
353             }
354             elsif(defined $param){
355 0         0 Carp::croak("Undefined type with parameter [$param] in '$c->{orig}'");
356             }
357             else{
358 10         19 return undef;
359             }
360             }
361              
362             # type : single_type ('|' single_type)*
363             sub _parse_type {
364 108     108   127 my($c, $create) = @_;
365              
366 108         223 my $type = _parse_single_type($c, $create);
367 107 100       242 if($c->{spec}){ # can be an union type
368 58         68 my @types;
369 58         182 while($c->{spec} =~ s/^\|//){
370 20         32 push @types, _parse_single_type($c, $create);
371             }
372 58 100       137 if(@types){
373 16         37 return _find_or_create_union_type($type, @types);
374             }
375             }
376 91         140 return $type;
377             }
378              
379              
380             sub find_type_constraint {
381 972     972 1 20449 my($spec) = @_;
382 972 100 66     4321 return $spec if Mouse::Util::is_a_type_constraint($spec) or not defined $spec;
383              
384 971         2026 $spec =~ s/\s+//g;
385 971         3937 return $TYPE{$spec};
386             }
387              
388             sub register_type_constraint {
389 2     2 0 7 my($constraint) = @_;
390 2 50       6 Carp::croak("No type supplied / type is not a valid type constraint")
391             unless Mouse::Util::is_a_type_constraint($constraint);
392 2         6 return $TYPE{$constraint->name} = $constraint;
393             }
394              
395             sub find_or_parse_type_constraint {
396 1251     1251 0 8131 my($spec) = @_;
397 1251 100 66     5163 return $spec if Mouse::Util::is_a_type_constraint($spec) or not defined $spec;
398              
399 1233         2019 $spec =~ tr/ \t\r\n//d;
400              
401 1233         1645 my $tc = $TYPE{$spec};
402 1233 100       2466 if(defined $tc) {
403 1169         1723 return $tc;
404             }
405              
406 64         214 my %context = (
407             spec => $spec,
408             orig => $spec,
409             );
410 64         192 $tc = _parse_type(\%context);
411              
412 63 50       166 if($context{spec}){
413 0         0 Carp::croak("Syntax error: extra elements '$context{spec}' in '$context{orig}'");
414             }
415              
416 63         209 return $TYPE{$spec} = $tc;
417             }
418              
419             sub find_or_create_does_type_constraint{
420             # XXX: Moose does not register a new role_type, but Mouse does.
421 5     5 0 13 my $tc = find_or_parse_type_constraint(@_);
422 5 50       25 return defined($tc) ? $tc : role_type(@_);
423             }
424              
425             sub find_or_create_isa_type_constraint {
426             # XXX: Moose does not register a new class_type, but Mouse does.
427 1196     1196 0 2042 my $tc = find_or_parse_type_constraint(@_);
428 1195 100       3129 return defined($tc) ? $tc : class_type(@_);
429             }
430              
431             1;
432             __END__