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