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