Move features used only for testing to t/lib/Test/Mouse.pm
[gitmo/Mouse.git] / lib / Mouse / Util / TypeConstraints.pm
CommitLineData
3b46bd49 1package Mouse::Util::TypeConstraints;
d60c78b9 2use strict;
3use warnings;
139d92d2 4use base 'Exporter';
9baf5d6b 5
61a02a3a 6use Carp ();
6c169c50 7use Scalar::Util qw/blessed looks_like_number openhandle/;
6d28c5cf 8
9use Mouse::Util;
684db121 10use Mouse::Meta::TypeConstraint;
d60c78b9 11
139d92d2 12our @EXPORT = qw(
d44f0d03 13 as where message from via type subtype coerce class_type role_type enum
ccf44227 14 find_type_constraint
139d92d2 15);
16
cceb0e25 17my %TYPE;
7dbebb1b 18my %TYPE_SOURCE;
8a7f2a8a 19my %COERCE;
20my %COERCE_KEYS;
4188b837 21
139d92d2 22sub as ($) {
cd2b9201 23 return(as => $_[0]);
61a02a3a 24}
139d92d2 25sub where (&) {
cd2b9201 26 return(where => $_[0])
61a02a3a 27}
0f1dae9a 28sub message (&) {
cd2b9201 29 return(message => $_[0])
61a02a3a 30}
31
73766a27 32sub from { @_ }
cd2b9201 33sub via (&) { $_[0] }
61a02a3a 34
321e5271 35BEGIN {
993e62a7 36 my %builtins = (
381f326a 37 Any => sub { 1 },
38 Item => sub { 1 },
73766a27 39
40 Bool => sub { $_[0] ? $_[0] eq '1' : 1 },
c91d12e0 41 Undef => sub { !defined($_[0]) },
42 Defined => sub { defined($_[0]) },
43 Value => sub { defined($_[0]) && !ref($_[0]) },
44 Num => sub { !ref($_[0]) && looks_like_number($_[0]) },
45 Int => sub { defined($_[0]) && !ref($_[0]) && $_[0] =~ /^-?[0-9]+$/ },
46 Str => sub { defined($_[0]) && !ref($_[0]) },
c91d12e0 47 Ref => sub { ref($_[0]) },
48
49 ScalarRef => sub { ref($_[0]) eq 'SCALAR' },
50 ArrayRef => sub { ref($_[0]) eq 'ARRAY' },
51 HashRef => sub { ref($_[0]) eq 'HASH' },
52 CodeRef => sub { ref($_[0]) eq 'CODE' },
53 RegexpRef => sub { ref($_[0]) eq 'Regexp' },
54 GlobRef => sub { ref($_[0]) eq 'GLOB' },
381f326a 55
56 FileHandle => sub {
c91d12e0 57 ref($_[0]) eq 'GLOB' && openhandle($_[0])
381f326a 58 or
c91d12e0 59 blessed($_[0]) && $_[0]->isa("IO::Handle")
abe4e887 60 },
381f326a 61
c91d12e0 62 Object => sub { blessed($_[0]) && blessed($_[0]) ne 'Regexp' },
73766a27 63
64 ClassName => sub { Mouse::Util::is_class_loaded($_[0]) },
d4571def 65 RoleName => sub { (Mouse::Util::find_meta($_[0]) || return 0)->isa('Mouse::Meta::Role') },
8a7f2a8a 66 );
993e62a7 67
68 while (my ($name, $code) = each %builtins) {
73766a27 69 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
70 name => $name,
71 _compiled_type_constraint => $code,
72 );
d4571def 73 $TYPE_SOURCE{$name} = __PACKAGE__;
78b13827 74 }
d3982c7e 75
cceb0e25 76 sub optimized_constraints { \%TYPE }
d4571def 77
993e62a7 78 my @builtins = keys %TYPE;
79 sub list_all_builtin_type_constraints { @builtins }
80
81 sub list_all_type_constraints { keys %TYPE }
381f326a 82}
d3982c7e 83
139d92d2 84sub type {
73766a27 85 my $name;
86 my %conf;
87
88 if(@_ == 1 && ref $_[0]){ # type { where => ... }
89 %conf = %{$_[0]};
90 }
91 elsif(@_ == 2 && ref $_[1]){ # type $name => { where => ... }*
92 $name = $_[0];
93 %conf = %{$_[1]};
94 }
95 elsif(@_ % 2){ # odd number of arguments
96 $name = shift;
97 %conf = @_;
98 }
99 else{
100 %conf = @_;
101 }
102
103 $name = '__ANON__' if !defined $name;
104
105 my $pkg = caller;
cd2b9201 106
0d062abb 107 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
7dbebb1b 108 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
cd2b9201 109 }
d4571def 110
321e5271 111 my $constraint = $conf{where} || do {
112 my $as = delete $conf{as} || 'Any';
d4571def 113 ($TYPE{$as} ||= _build_type_constraint($as))->{_compiled_type_constraint};
321e5271 114 };
7dbebb1b 115
d4571def 116 my $tc = Mouse::Meta::TypeConstraint->new(
117 name => $name,
684db121 118 _compiled_type_constraint => sub {
119 local $_ = $_[0];
d4571def 120 return &{$constraint};
121 },
684db121 122 );
d4571def 123
124 $TYPE_SOURCE{$name} = $pkg;
125 $TYPE{$name} = $tc;
126
127 return $tc;
0d9fea22 128}
129
139d92d2 130sub subtype {
7a50b450 131 my $name;
132 my %conf;
133
73766a27 134 if(@_ == 1 && ref $_[0]){ # type { where => ... }
135 %conf = %{$_[0]};
136 }
137 elsif(@_ == 2 && ref $_[1]){ # type $name => { where => ... }*
138 $name = $_[0];
139 %conf = %{$_[1]};
140 }
141 elsif(@_ % 2){ # odd number of arguments
7a50b450 142 $name = shift;
143 %conf = @_;
144 }
145 else{
146 %conf = @_;
7a50b450 147 }
148
73766a27 149 $name = '__ANON__' if !defined $name;
150
151 my $pkg = caller;
152
0d062abb 153 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
7dbebb1b 154 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
73766a27 155 }
7dbebb1b 156
d4571def 157 my $constraint = delete $conf{where};
158 my $as_constraint = find_or_create_isa_type_constraint(delete $conf{as} || 'Any')
159 ->{_compiled_type_constraint};
160
161 my $tc = Mouse::Meta::TypeConstraint->new(
684db121 162 name => $name,
163 _compiled_type_constraint => (
164 $constraint ?
165 sub {
166 local $_ = $_[0];
d4571def 167 $as_constraint->($_[0]) && $constraint->($_[0])
684db121 168 } :
169 sub {
170 local $_ = $_[0];
d4571def 171 $as_constraint->($_[0]);
684db121 172 }
173 ),
d4571def 174 %conf,
684db121 175 );
7dbebb1b 176
d4571def 177 $TYPE_SOURCE{$name} = $pkg;
178 $TYPE{$name} = $tc;
179
180 return $tc;
4188b837 181}
182
139d92d2 183sub coerce {
cd2b9201 184 my $name = shift;
61a02a3a 185
186 Carp::croak "Cannot find type '$name', perhaps you forgot to load it."
cceb0e25 187 unless $TYPE{$name};
61a02a3a 188
8a7f2a8a 189 unless ($COERCE{$name}) {
190 $COERCE{$name} = {};
191 $COERCE_KEYS{$name} = [];
192 }
cd2b9201 193
194 while (my($type, $code) = splice @_, 0, 2) {
61a02a3a 195 Carp::croak "A coercion action already exists for '$type'"
8a7f2a8a 196 if $COERCE{$name}->{$type};
61a02a3a 197
310ad28b 198 if (! $TYPE{$type}) {
199 # looks parameterized
200 if ($type =~ /^[^\[]+\[.+\]$/) {
94593ae8 201 $TYPE{$type} = _build_type_constraint($type);
310ad28b 202 } else {
203 Carp::croak "Could not find the type constraint ($type) to coerce from"
204 }
205 }
61a02a3a 206
cd2b9201 207 push @{ $COERCE_KEYS{$name} }, $type;
8a7f2a8a 208 $COERCE{$name}->{$type} = $code;
61a02a3a 209 }
cd2b9201 210 return;
4188b837 211}
212
139d92d2 213sub class_type {
ecc6e3b1 214 my($name, $conf) = @_;
d9f8c878 215 if ($conf && $conf->{class}) {
216 # No, you're using this wrong
217 warn "class_type() should be class_type(ClassName). Perhaps you're looking for subtype $name => as '$conf->{class}'?";
218 subtype($name, as => $conf->{class});
219 } else {
220 subtype(
221 $name => where => sub { $_->isa($name) }
222 );
223 }
ecc6e3b1 224}
225
139d92d2 226sub role_type {
47f36c05 227 my($name, $conf) = @_;
228 my $role = $conf->{role};
139d92d2 229 subtype(
61a02a3a 230 $name => where => sub {
231 return unless defined $_ && ref($_) && $_->isa('Mouse::Object');
232 $_->meta->does_role($role);
233 }
234 );
47f36c05 235}
236
684db121 237# this is an original method for Mouse
4188b837 238sub typecast_constraints {
684db121 239 my($class, $pkg, $types, $value) = @_;
86b99892 240 Carp::croak("wrong arguments count") unless @_==4;
eec1bb49 241
b3b74cc6 242 local $_;
684db121 243 for my $type ( split /\|/, $types ) {
8a7f2a8a 244 next unless $COERCE{$type};
8a7f2a8a 245 for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
b3b74cc6 246 $_ = $value;
684db121 247 next unless $TYPE{$coerce_type}->check($value);
b3b74cc6 248 $_ = $value;
c91d12e0 249 $_ = $COERCE{$type}->{$coerce_type}->($value);
684db121 250 return $_ if $types->check($_);
4188b837 251 }
252 }
4188b837 253 return $value;
254}
255
01904723 256my $serial_enum = 0;
d44f0d03 257sub enum {
01904723 258 # enum ['small', 'medium', 'large']
259 if (ref($_[0]) eq 'ARRAY') {
260 my @elements = @{ shift @_ };
261
262 my $name = 'Mouse::Util::TypeConstaints::Enum::Serial::'
263 . ++$serial_enum;
264 enum($name, @elements);
265 return $name;
266 }
267
268 # enum size => 'small', 'medium', 'large'
d44f0d03 269 my $name = shift;
270 my %is_valid = map { $_ => 1 } @_;
271
272 subtype(
273 $name => where => sub { $is_valid{$_} }
274 );
275}
276
321e5271 277sub _build_type_constraint {
278
279 my $spec = shift;
280 my $code;
94593ae8 281 $spec =~ s/\s+//g;
321e5271 282 if ($spec =~ /^([^\[]+)\[(.+)\]$/) {
283 # parameterized
284 my $constraint = $1;
285 my $param = $2;
286 my $parent;
287 if ($constraint eq 'Maybe') {
288 $parent = _build_type_constraint('Undef');
289 } else {
290 $parent = _build_type_constraint($constraint);
291 }
292 my $child = _build_type_constraint($param);
293 if ($constraint eq 'ArrayRef') {
294 my $code_str =
295 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
296 "sub {\n" .
684db121 297 " if (\$parent->check(\$_[0])) {\n" .
321e5271 298 " foreach my \$e (\@{\$_[0]}) {\n" .
684db121 299 " return () unless \$child->check(\$e);\n" .
321e5271 300 " }\n" .
301 " return 1;\n" .
302 " }\n" .
303 " return ();\n" .
304 "};\n"
305 ;
306 $code = eval $code_str or Carp::confess("Failed to generate inline type constraint: $@");
307 } elsif ($constraint eq 'HashRef') {
308 my $code_str =
309 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
310 "sub {\n" .
684db121 311 " if (\$parent->check(\$_[0])) {\n" .
321e5271 312 " foreach my \$e (values \%{\$_[0]}) {\n" .
684db121 313 " return () unless \$child->check(\$e);\n" .
321e5271 314 " }\n" .
315 " return 1;\n" .
316 " }\n" .
317 " return ();\n" .
318 "};\n"
319 ;
320 $code = eval $code_str or Carp::confess($@);
321 } elsif ($constraint eq 'Maybe') {
322 my $code_str =
323 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
324 "sub {\n" .
684db121 325 " return \$child->check(\$_[0]) || \$parent->check(\$_[0]);\n" .
321e5271 326 "};\n"
327 ;
328 $code = eval $code_str or Carp::confess($@);
329 } else {
766534c2 330 Carp::confess("Support for parameterized types other than Maybe, ArrayRef or HashRef is not implemented yet");
321e5271 331 }
684db121 332 $TYPE{$spec} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
321e5271 333 } else {
334 $code = $TYPE{ $spec };
335 if (! $code) {
5c5a61e0 336 # is $spec a known role? If so, constrain with 'does' instead of 'isa'
337 require Mouse::Meta::Role;
338 my $check = Mouse::Meta::Role->_metaclass_cache($spec)?
339 'does' : 'isa';
321e5271 340 my $code_str =
341 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
342 "sub {\n" .
5c5a61e0 343 " Scalar::Util::blessed(\$_[0]) && \$_[0]->$check('$spec');\n" .
321e5271 344 "}"
345 ;
346 $code = eval $code_str or Carp::confess($@);
684db121 347 $TYPE{$spec} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
321e5271 348 }
349 }
684db121 350 return Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
321e5271 351}
352
353sub find_type_constraint {
993e62a7 354 my($type) = @_;
355 if(blessed($type) && $type->isa('Mouse::Meta::TypeConstraint')){
356 return $type;
357 }
358 else{
359 return $TYPE{$type};
360 }
321e5271 361}
362
363sub find_or_create_isa_type_constraint {
364 my $type_constraint = shift;
365
9c85e9dc 366 Carp::confess("Got isa => type_constraints, but Mouse does not yet support parameterized types for containers other than ArrayRef and HashRef and Maybe (rt.cpan.org #39795)")
367 if $type_constraint =~ /\A ( [^\[]+ ) \[\.+\] \z/xms &&
368 $1 ne 'ArrayRef' &&
369 $1 ne 'HashRef' &&
370 $1 ne 'Maybe'
371 ;
372
321e5271 373 my $code;
374
375 $type_constraint =~ s/\s+//g;
94593ae8 376
377 $code = $TYPE{$type_constraint};
378 if (! $code) {
379 my @type_constraints = split /\|/, $type_constraint;
380 if (@type_constraints == 1) {
381 $code = $TYPE{$type_constraints[0]} ||
382 _build_type_constraint($type_constraints[0]);
383 } else {
384 my @code_list = map {
385 $TYPE{$_} || _build_type_constraint($_)
386 } @type_constraints;
684db121 387 $code = Mouse::Meta::TypeConstraint->new(
388 _compiled_type_constraint => sub {
389 my $i = 0;
390 for my $code (@code_list) {
391 return 1 if $code->check($_[0]);
392 }
393 return 0;
394 },
395 name => $type_constraint,
396 );
94593ae8 397 }
321e5271 398 }
399 return $code;
400}
401
d60c78b9 4021;
403
6feb83f1 404__END__
405
406=head1 NAME
407
5893ee36 408Mouse::Util::TypeConstraints - Type constraint system for Mouse
409
410=head2 SYNOPSIS
411
412 use Mouse::Util::TypeConstraints;
413
414 subtype 'Natural'
415 => as 'Int'
416 => where { $_ > 0 };
417
418 subtype 'NaturalLessThanTen'
419 => as 'Natural'
420 => where { $_ < 10 }
421 => message { "This number ($_) is not less than ten!" };
422
423 coerce 'Num'
424 => from 'Str'
425 => via { 0+$_ };
426
427 enum 'RGBColors' => qw(red green blue);
428
429 no Mouse::Util::TypeConstraints;
430
431=head1 DESCRIPTION
432
433This module provides Mouse with the ability to create custom type
434constraints to be used in attribute definition.
435
436=head2 Important Caveat
437
438This is B<NOT> a type system for Perl 5. These are type constraints,
439and they are not used by Mouse unless you tell it to. No type
440inference is performed, expressions are not typed, etc. etc. etc.
441
442A type constraint is at heart a small "check if a value is valid"
443function. A constraint can be associated with an attribute. This
444simplifies parameter validation, and makes your code clearer to read,
445because you can refer to constraints by name.
446
447=head2 Slightly Less Important Caveat
448
449It is B<always> a good idea to quote your type names.
450
451This prevents Perl from trying to execute the call as an indirect
452object call. This can be an issue when you have a subtype with the
453same name as a valid class.
454
455For instance:
456
457 subtype DateTime => as Object => where { $_->isa('DateTime') };
458
459will I<just work>, while this:
460
461 use DateTime;
462 subtype DateTime => as Object => where { $_->isa('DateTime') };
463
464will fail silently and cause many headaches. The simple way to solve
465this, as well as future proof your subtypes from classes which have
466yet to have been created, is to quote the type name:
467
468 use DateTime;
469 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
470
471=head2 Default Type Constraints
472
473This module also provides a simple hierarchy for Perl 5 types, here is
474that hierarchy represented visually.
475
476 Any
477 Item
478 Bool
479 Maybe[`a]
480 Undef
481 Defined
482 Value
483 Num
484 Int
485 Str
486 ClassName
487 RoleName
488 Ref
489 ScalarRef
490 ArrayRef[`a]
491 HashRef[`a]
492 CodeRef
493 RegexpRef
494 GlobRef
495 FileHandle
496 Object
497 Role
498
499B<NOTE:> Any type followed by a type parameter C<[`a]> can be
500parameterized, this means you can say:
501
502 ArrayRef[Int] # an array of integers
503 HashRef[CodeRef] # a hash of str to CODE ref mappings
504 Maybe[Str] # value may be a string, may be undefined
505
506If Mouse finds a name in brackets that it does not recognize as an
507existing type, it assumes that this is a class name, for example
508C<ArrayRef[DateTime]>.
509
510B<NOTE:> Unless you parameterize a type, then it is invalid to include
511the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
512name, I<not> as a parameterization of C<ArrayRef>.
513
514B<NOTE:> The C<Undef> type constraint for the most part works
515correctly now, but edge cases may still exist, please use it
516sparingly.
517
518B<NOTE:> The C<ClassName> type constraint does a complex package
519existence check. This means that your class B<must> be loaded for this
520type constraint to pass.
521
522B<NOTE:> The C<RoleName> constraint checks a string is a I<package
523name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
524constraint checks that an I<object does> the named role.
525
526=head2 Type Constraint Naming
527
528Type name declared via this module can only contain alphanumeric
529characters, colons (:), and periods (.).
530
531Since the types created by this module are global, it is suggested
532that you namespace your types just as you would namespace your
533modules. So instead of creating a I<Color> type for your
534B<My::Graphics> module, you would call the type
535I<My::Graphics::Types::Color> instead.
536
537=head2 Use with Other Constraint Modules
538
539This module can play nicely with other constraint modules with some
540slight tweaking. The C<where> clause in types is expected to be a
541C<CODE> reference which checks it's first argument and returns a
542boolean. Since most constraint modules work in a similar way, it
543should be simple to adapt them to work with Mouse.
544
545For instance, this is how you could use it with
546L<Declare::Constraints::Simple> to declare a completely new type.
547
548 type 'HashOfArrayOfObjects',
549 {
550 where => IsHashRef(
551 -keys => HasLength,
552 -values => IsArrayRef(IsObject)
553 )
554 };
555
556Here is an example of using L<Test::Deep> and it's non-test
557related C<eq_deeply> function.
558
559 type 'ArrayOfHashOfBarsAndRandomNumbers'
560 => where {
561 eq_deeply($_,
562 array_each(subhashof({
563 bar => isa('Bar'),
564 random_number => ignore()
565 })))
566 };
6feb83f1 567
568=head1 METHODS
569
570=head2 optimized_constraints -> HashRef[CODE]
571
572Returns the simple type constraints that Mouse understands.
573
c91d12e0 574=head1 FUNCTIONS
575
576=over 4
577
578=item B<subtype 'Name' => as 'Parent' => where { } ...>
579
580=item B<subtype as 'Parent' => where { } ...>
581
582=item B<class_type ($class, ?$options)>
583
584=item B<role_type ($role, ?$options)>
585
586=item B<enum (\@values)>
587
588=back
589
5893ee36 590=head1 THANKS
591
592Much of this documentation was taken from L<Moose::Util::TypeConstraints>
593
6feb83f1 594=cut
595
596