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   147901 use Mouse::Util; # enables strict and warnings
  283         333  
  283         1253  
3              
4 283     283   4259 use Mouse::Meta::TypeConstraint;
  283         328  
  283         4516  
5 283     283   891 use Mouse::Exporter;
  283         278  
  283         1174  
6              
7 283     283   958 use Carp ();
  283         310  
  283         3237  
8 283     283   819 use Scalar::Util ();
  283         309  
  283         667132  
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 12806 sub as ($) { (as => $_[0]) } ## no critic
84 58     58 1 3014 sub where (&) { (where => $_[0]) } ## no critic
85 7     7 0 25 sub message (&) { (message => $_[0]) } ## no critic
86 0     0 0 0 sub optimize_as (&) { (optimize_as => $_[0]) } ## no critic
87              
88 37     37 1 111 sub from { @_ }
89 37     37 1 3164 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 6 sub list_all_builtin_type_constraints { @builtins }
101 5     5 1 563 sub list_all_type_constraints { keys %TYPE }
102              
103             sub _define_type {
104 853     853   884 my $is_subtype = shift;
105 853         709 my $name;
106             my %args;
107              
108 853 50 33     4748 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         2664 ($name, %args) = @_;
117             }
118             else{ # @_ : (name => $name, where => ...)
119 3         9 %args = @_;
120             }
121              
122 853 100       1868 if(!defined $name){
123 7         15 $name = $args{name};
124             }
125              
126 853         1141 $args{name} = $name;
127              
128 853         1138 my $parent = delete $args{as};
129 853 100 100     3423 if($is_subtype && !$parent){
130 3         8 $parent = delete $args{name};
131 3         5 $name = undef;
132             }
133              
134 853 100       1488 if(defined $parent) {
135 838         1398 $args{parent} = find_or_create_isa_type_constraint($parent);
136             }
137              
138 852 100       1422 if(defined $name){
139             # set 'package_defined_in' only if it is not a core package
140 842         872 my $this = $args{package_defined_in};
141 842 50       1342 if(!$this){
142 842         1350 $this = caller(1);
143 842 100       6204 if($this !~ /\A Mouse \b/xms){
144 79         138 $args{package_defined_in} = $this;
145             }
146             }
147              
148 842 100       1701 if(defined $TYPE{$name}){
149 10   100     97 my $that = $TYPE{$name}->{package_defined_in} || __PACKAGE__;
150 10 100       30 if($this ne $that) {
151 3         6 my $note = '';
152 3 100       8 if($that eq __PACKAGE__) {
153             $note = sprintf " ('%s' is %s type constraint)",
154             $name,
155 1 50       4 scalar(grep { $name eq $_ } list_all_builtin_type_constraints())
  21         25  
156             ? 'a builtin'
157             : 'an implicitly created';
158             }
159 3         505 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       1494 $args{constraint} = delete $args{where} if exists $args{where};
166 849 100       1921 $args{optimized} = delete $args{optimized_as} if exists $args{optimized_as};
167              
168 849         3670 my $constraint = Mouse::Meta::TypeConstraint->new(%args);
169              
170 849 100       1618 if(defined $name){
171 839         2734 return $TYPE{$name} = $constraint;
172             }
173             else{
174 10         41 return $constraint;
175             }
176             }
177              
178             sub type {
179 15     15 1 39 return _define_type 0, @_;
180             }
181              
182             sub subtype {
183 825     825 1 1495 return _define_type 1, @_;
184             }
185              
186             sub coerce { # coerce $type, from $from, via { ... }, ...
187 31     31 1 44 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         118 $type->_add_type_coercions(@_);
192 28         46 return;
193             }
194              
195             sub class_type {
196 570     570 1 704 my($name, $options) = @_;
197 570   33     2091 my $class = $options->{class} || $name;
198              
199             # ClassType
200 570         3595 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 282 my($name, $options) = @_;
209 193   66     939 my $role = $options->{role} || $name;
210              
211             # RoleType
212             return subtype $name => (
213             as => 'Object',
214             optimized_as => sub {
215 23   100 23   3919 return Scalar::Util::blessed($_[0])
216             && Mouse::Util::does_role($_[0], $role);
217             },
218 193         782 role => $role,
219             );
220             }
221              
222             sub maybe_type {
223 1     1 0 2 my $param = shift;
224 1         3 return _find_or_create_parameterized_type($TYPE{Maybe}, $param);
225             }
226              
227             sub duck_type {
228 6     6 1 31 my($name, @methods);
229              
230 6 100       15 if(ref($_[0]) ne 'ARRAY'){
231 4         7 $name = shift;
232             }
233              
234 6 100 66     56 @methods = (@_ == 1 && ref($_[0]) eq 'ARRAY') ? @{$_[0]} : @_;
  4         6  
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         5  
243 1         10 return ref($object)
244             . ' is missing methods '
245             . Mouse::Util::quoted_english_list(@missing);
246             },
247 6         78 methods => \@methods,
248             );
249             }
250              
251             sub enum {
252 7     7 1 1048 my($name, %valid);
253              
254 7 100 66     47 if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){
255 5         7 $name = shift;
256             }
257              
258 78         120 %valid = map{ $_ => undef }
259 7 100 66     37 (@_ == 1 && ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_);
  3         10  
260              
261             # EnumType
262             return _define_type 1, $name => (
263             as => 'Str',
264             optimized_as => sub{
265 120   66 120   32940 return defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]};
266             },
267 7         49 );
268             }
269              
270             sub _find_or_create_regular_type{
271 128     128   215 my($spec, $create) = @_;
272              
273 128 100       451 return $TYPE{$spec} if exists $TYPE{$spec};
274              
275 13         50 my $meta = Mouse::Util::get_metaclass_by_name($spec);
276              
277 13 50       37 if(!defined $meta){
278 13 100       44 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   52 my($base, $param) = @_;
291              
292 45         307 my $name = sprintf '%s[%s]', $base->name, $param->name;
293              
294 45   100     254 $TYPE{$name} ||= $base->parameterize($param, $name);
295             }
296              
297             sub _find_or_create_union_type{
298 22 50   22   32 return if grep{ not defined } @_; # all things must be defined
  48         92  
299             my @types = sort
300 22 100       27 map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
  48         277  
  2         3  
301              
302 22         53 my $name = join '|', @types;
303              
304             # UnionType
305 22   66     145 $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   245 my($c) = @_;
316              
317 128 100       474 if($c->{spec} =~ s/^\[//){
318 44         118 my $type = _parse_type($c, 1);
319              
320 44 50       176 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   97 my($c, $create) = @_;
332              
333 128 50       551 if($c->{spec} =~ s/\A ([\w.:]+) //xms){
334 128         234 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   116 my($c, $create) = @_;
342              
343 128         187 my $type = _parse_name($c, $create);
344 128         227 my $param = _parse_param($c);
345              
346 128 100       228 if(defined $type){
    50          
347 118 100       150 if(defined $param){
348 44         89 return _find_or_create_parameterized_type($type, $param);
349             }
350             else {
351 74         127 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         15 return undef;
359             }
360             }
361              
362             # type : single_type ('|' single_type)*
363             sub _parse_type {
364 108     108   117 my($c, $create) = @_;
365              
366 108         202 my $type = _parse_single_type($c, $create);
367 107 100       238 if($c->{spec}){ # can be an union type
368 58         57 my @types;
369 58         167 while($c->{spec} =~ s/^\|//){
370 20         27 push @types, _parse_single_type($c, $create);
371             }
372 58 100       125 if(@types){
373 16         37 return _find_or_create_union_type($type, @types);
374             }
375             }
376 91         106 return $type;
377             }
378              
379              
380             sub find_type_constraint {
381 972     972 1 21362 my($spec) = @_;
382 972 100 66     4489 return $spec if Mouse::Util::is_a_type_constraint($spec) or not defined $spec;
383              
384 971         2075 $spec =~ s/\s+//g;
385 971         4047 return $TYPE{$spec};
386             }
387              
388             sub register_type_constraint {
389 2     2 0 7 my($constraint) = @_;
390 2 50       8 Carp::croak("No type supplied / type is not a valid type constraint")
391             unless Mouse::Util::is_a_type_constraint($constraint);
392 2         7 return $TYPE{$constraint->name} = $constraint;
393             }
394              
395             sub find_or_parse_type_constraint {
396 1251     1251 0 8502 my($spec) = @_;
397 1251 100 66     5227 return $spec if Mouse::Util::is_a_type_constraint($spec) or not defined $spec;
398              
399 1233         1999 $spec =~ tr/ \t\r\n//d;
400              
401 1233         1693 my $tc = $TYPE{$spec};
402 1233 100       2476 if(defined $tc) {
403 1169         1759 return $tc;
404             }
405              
406 64         198 my %context = (
407             spec => $spec,
408             orig => $spec,
409             );
410 64         168 $tc = _parse_type(\%context);
411              
412 63 50       157 if($context{spec}){
413 0         0 Carp::croak("Syntax error: extra elements '$context{spec}' in '$context{orig}'");
414             }
415              
416 63         177 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       24 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 2069 my $tc = find_or_parse_type_constraint(@_);
428 1195 100       3123 return defined($tc) ? $tc : class_type(@_);
429             }
430              
431             1;
432             __END__