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