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