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