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