Switch to Mouse::Exporter
[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(
12 as where message from via
13 type subtype coerce class_type role_type enum
14 find_type_constraint
15 )],
d60c78b9 16
bc69ee88 17 _export_to_main => 1,
139d92d2 18);
19
cceb0e25 20my %TYPE;
4188b837 21
139d92d2 22sub as ($) {
cd2b9201 23 return(as => $_[0]);
61a02a3a 24}
139d92d2 25sub where (&) {
cd2b9201 26 return(where => $_[0])
61a02a3a 27}
0f1dae9a 28sub message (&) {
cd2b9201 29 return(message => $_[0])
61a02a3a 30}
31
73766a27 32sub from { @_ }
cd2b9201 33sub via (&) { $_[0] }
61a02a3a 34
321e5271 35BEGIN {
993e62a7 36 my %builtins = (
f5ee065f 37 Any => undef, # null check
38 Item => undef, # null check
39 Maybe => undef, # null check
73766a27 40
41 Bool => sub { $_[0] ? $_[0] eq '1' : 1 },
c91d12e0 42 Undef => sub { !defined($_[0]) },
43 Defined => sub { defined($_[0]) },
44 Value => sub { defined($_[0]) && !ref($_[0]) },
45 Num => sub { !ref($_[0]) && looks_like_number($_[0]) },
46 Int => sub { defined($_[0]) && !ref($_[0]) && $_[0] =~ /^-?[0-9]+$/ },
47 Str => sub { defined($_[0]) && !ref($_[0]) },
c91d12e0 48 Ref => sub { ref($_[0]) },
49
50 ScalarRef => sub { ref($_[0]) eq 'SCALAR' },
51 ArrayRef => sub { ref($_[0]) eq 'ARRAY' },
52 HashRef => sub { ref($_[0]) eq 'HASH' },
53 CodeRef => sub { ref($_[0]) eq 'CODE' },
54 RegexpRef => sub { ref($_[0]) eq 'Regexp' },
55 GlobRef => sub { ref($_[0]) eq 'GLOB' },
381f326a 56
57 FileHandle => sub {
c91d12e0 58 ref($_[0]) eq 'GLOB' && openhandle($_[0])
381f326a 59 or
c91d12e0 60 blessed($_[0]) && $_[0]->isa("IO::Handle")
abe4e887 61 },
381f326a 62
c91d12e0 63 Object => sub { blessed($_[0]) && blessed($_[0]) ne 'Regexp' },
73766a27 64
65 ClassName => sub { Mouse::Util::is_class_loaded($_[0]) },
d4571def 66 RoleName => sub { (Mouse::Util::find_meta($_[0]) || return 0)->isa('Mouse::Meta::Role') },
8a7f2a8a 67 );
993e62a7 68
69 while (my ($name, $code) = each %builtins) {
73766a27 70 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
3b89ea91 71 name => $name,
72 optimized => $code,
73766a27 73 );
78b13827 74 }
d3982c7e 75
deb9a0f3 76 sub optimized_constraints { # DEPRECATED
f5ee065f 77 Carp::cluck('optimized_constraints() has been deprecated');
78 return \%TYPE;
79 }
d4571def 80
993e62a7 81 my @builtins = keys %TYPE;
82 sub list_all_builtin_type_constraints { @builtins }
83
84 sub list_all_type_constraints { keys %TYPE }
381f326a 85}
d3982c7e 86
f5ee065f 87sub _create_type{
88 my $mode = shift;
89
73766a27 90 my $name;
f5ee065f 91 my %args;
73766a27 92
f5ee065f 93 if(@_ == 1 && ref $_[0]){ # @_ : { name => $name, where => ... }
94 %args = %{$_[0]};
73766a27 95 }
f5ee065f 96 elsif(@_ == 2 && ref $_[1]){ # @_ : $name => { where => ... }
73766a27 97 $name = $_[0];
f5ee065f 98 %args = %{$_[1]};
73766a27 99 }
f5ee065f 100 elsif(@_ % 2){ # @_ : $name => ( where => ... )
101 ($name, %args) = @_;
73766a27 102 }
f5ee065f 103 else{ # @_ : (name => $name, where => ...)
104 %args = @_;
73766a27 105 }
106
f5ee065f 107 if(!defined $name){
108 if(!defined($name = $args{name})){
109 $name = '__ANON__';
110 }
cd2b9201 111 }
d4571def 112
f5ee065f 113 $args{name} = $name;
7dbebb1b 114
f5ee065f 115 my $package_defined_in = $args{package_defined_in} ||= caller(1);
d4571def 116
f5ee065f 117 my $existing = $TYPE{$name};
118 if($existing && $existing->{package_defined_in} ne $package_defined_in){
119 confess("The type constraint '$name' has already been created in "
120 . "$existing->{package_defined_in} and cannot be created again in $package_defined_in");
121 }
d4571def 122
24410e3a 123 $args{constraint} = delete($args{where}) if exists $args{where};
124 $args{optimized} = delete $args{optimized_as} if exists $args{optimized_as};
0d9fea22 125
f5ee065f 126 my $constraint;
127 if($mode eq 'subtype'){
24410e3a 128 my $parent = delete($args{as})
129 or confess('A subtype cannot consist solely of a name, it must have a parent');
7a50b450 130
24410e3a 131 $constraint = find_or_create_isa_type_constraint($parent)->create_child_type(%args);
7a50b450 132 }
133 else{
f5ee065f 134 $constraint = Mouse::Meta::TypeConstraint->new(%args);
73766a27 135 }
7dbebb1b 136
f5ee065f 137 return $TYPE{$name} = $constraint;
138}
7dbebb1b 139
f5ee065f 140sub type {
141 return _create_type('type', @_);
142}
d4571def 143
f5ee065f 144sub subtype {
145 return _create_type('subtype', @_);
4188b837 146}
147
139d92d2 148sub coerce {
ffbbf459 149 my $type_name = shift;
61a02a3a 150
ffbbf459 151 my $type = find_type_constraint($type_name)
152 or confess("Cannot find type '$type_name', perhaps you forgot to load it.");
61a02a3a 153
ffbbf459 154 $type->_add_type_coercions(@_);
cd2b9201 155 return;
4188b837 156}
157
139d92d2 158sub class_type {
ecc6e3b1 159 my($name, $conf) = @_;
d9f8c878 160 if ($conf && $conf->{class}) {
161 # No, you're using this wrong
162 warn "class_type() should be class_type(ClassName). Perhaps you're looking for subtype $name => as '$conf->{class}'?";
f5ee065f 163 _create_type 'type', $name => (
164 as => $conf->{class},
165
166 type => 'Class',
167 );
a497c7d3 168 }
169 else {
f5ee065f 170 _create_type 'type', $name => (
171 optimized_as => sub { blessed($_[0]) && $_[0]->isa($name) },
172
173 type => 'Class',
d9f8c878 174 );
175 }
ecc6e3b1 176}
177
139d92d2 178sub role_type {
47f36c05 179 my($name, $conf) = @_;
f5ee065f 180 my $role = ($conf && $conf->{role}) ? $conf->{role} : $name;
181 _create_type 'type', $name => (
182 optimized_as => sub { blessed($_[0]) && does_role($_[0], $role) },
183
184 type => 'Role',
61a02a3a 185 );
47f36c05 186}
187
deb9a0f3 188sub typecast_constraints { # DEPRECATED
ffbbf459 189 my($class, $pkg, $type, $value) = @_;
2efc0af1 190 Carp::croak("wrong arguments count") unless @_ == 4;
eec1bb49 191
e763d56e 192 Carp::cluck("typecast_constraints() has been deprecated, which was an internal utility anyway");
193
ffbbf459 194 return $type->coerce($value);
4188b837 195}
196
d44f0d03 197sub enum {
f5ee065f 198 my($name, %valid);
199
01904723 200 # enum ['small', 'medium', 'large']
201 if (ref($_[0]) eq 'ARRAY') {
f5ee065f 202 %valid = map{ $_ => undef } @{ $_[0] };
203 $name = sprintf '(%s)', join '|', sort @{$_[0]};
204 }
205 # enum size => 'small', 'medium', 'large'
206 else{
207 $name = shift;
208 %valid = map{ $_ => undef } @_;
209 }
210 return _create_type 'type', $name => (
211 optimized_as => sub{ defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]} },
212
213 type => 'Enum',
214 );
215}
216
217sub _find_or_create_regular_type{
218 my($spec) = @_;
219
220 return $TYPE{$spec} if exists $TYPE{$spec};
01904723 221
7727a2f0 222 my $meta = Mouse::Util::get_metaclass_by_name($spec);
f5ee065f 223
224 if(!$meta){
225 return;
01904723 226 }
227
f5ee065f 228 my $check;
229 my $type;
57f0e313 230 if($meta->isa('Mouse::Meta::Role')){
f5ee065f 231 $check = sub{
232 return blessed($_[0]) && $_[0]->does($spec);
233 };
234 $type = 'Role';
235 }
236 else{
237 $check = sub{
238 return blessed($_[0]) && $_[0]->isa($spec);
239 };
240 $type = 'Class';
241 }
242
f5ee065f 243 return $TYPE{$spec} = Mouse::Meta::TypeConstraint->new(
3b89ea91 244 name => $spec,
245 optimized => $check,
f5ee065f 246
3b89ea91 247 type => $type,
d44f0d03 248 );
249}
250
f5ee065f 251$TYPE{ArrayRef}{constraint_generator} = sub {
252 my($type_parameter) = @_;
3b89ea91 253 my $check = $type_parameter->_compiled_type_constraint;
321e5271 254
f5ee065f 255 return sub{
256 foreach my $value (@{$_}) {
257 return undef unless $check->($value);
258 }
259 return 1;
260 }
261};
262$TYPE{HashRef}{constraint_generator} = sub {
263 my($type_parameter) = @_;
3b89ea91 264 my $check = $type_parameter->_compiled_type_constraint;
f5ee065f 265
266 return sub{
267 foreach my $value(values %{$_}){
268 return undef unless $check->($value);
269 }
270 return 1;
271 };
272};
2efc0af1 273
f5ee065f 274# 'Maybe' type accepts 'Any', so it requires parameters
275$TYPE{Maybe}{constraint_generator} = sub {
276 my($type_parameter) = @_;
3b89ea91 277 my $check = $type_parameter->_compiled_type_constraint;
2efc0af1 278
f5ee065f 279 return sub{
280 return !defined($_) || $check->($_);
281 };
282};
283
284sub _find_or_create_parameterized_type{
285 my($base, $param) = @_;
286
287 my $name = sprintf '%s[%s]', $base->name, $param->name;
288
289 $TYPE{$name} ||= do{
f5ee065f 290 my $generator = $base->{constraint_generator};
291
292 if(!$generator){
293 confess("The $name constraint cannot be used, because $param doesn't subtype from a parameterizable type");
2efc0af1 294 }
f5ee065f 295
296 Mouse::Meta::TypeConstraint->new(
297 name => $name,
298 parent => $base,
299 constraint => $generator->($param),
300
301 type => 'Parameterized',
302 );
303 }
304}
305sub _find_or_create_union_type{
57f0e313 306 my @types = map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
f5ee065f 307
308 my $name = join '|', map{ $_->name } @types;
309
310 $TYPE{$name} ||= do{
f5ee065f 311 return Mouse::Meta::TypeConstraint->new(
3b89ea91 312 name => $name,
313 type_constraints => \@types,
f5ee065f 314
3b89ea91 315 type => 'Union',
f5ee065f 316 );
317 };
318}
319
320# The type parser
321sub _parse_type{
322 my($spec, $start) = @_;
323
324 my @list;
325 my $subtype;
326
327 my $len = length $spec;
328 my $i;
329
330 for($i = $start; $i < $len; $i++){
331 my $char = substr($spec, $i, 1);
332
333 if($char eq '['){
57f0e313 334 my $base = _find_or_create_regular_type( substr($spec, $start, $i - $start) )
f5ee065f 335 or return;
336
337 ($i, $subtype) = _parse_type($spec, $i+1)
338 or return;
339 $start = $i+1; # reset
340
341 push @list, _find_or_create_parameterized_type($base => $subtype);
321e5271 342 }
f5ee065f 343 elsif($char eq ']'){
344 $len = $i+1;
345 last;
321e5271 346 }
f5ee065f 347 elsif($char eq '|'){
57f0e313 348 my $type = _find_or_create_regular_type( substr($spec, $start, $i - $start) );
349
57f0e313 350 if(!defined $type){
3b89ea91 351 # XXX: Mouse creates a new class type, but Moose does not.
352 $type = class_type( substr($spec, $start, $i - $start) );
57f0e313 353 }
f5ee065f 354
355 push @list, $type;
356
357 ($i, $subtype) = _parse_type($spec, $i+1)
358 or return;
359
360 $start = $i+1; # reset
361
362 push @list, $subtype;
321e5271 363 }
364 }
f5ee065f 365 if($i - $start){
366 push @list, _find_or_create_regular_type(substr $spec, $start, $i - $start);
367 }
321e5271 368
f5ee065f 369 if(@list == 0){
370 return;
371 }
372 elsif(@list == 1){
373 return ($len, $list[0]);
993e62a7 374 }
375 else{
f5ee065f 376 return ($len, _find_or_create_union_type(@list));
993e62a7 377 }
321e5271 378}
379
f5ee065f 380
381sub find_type_constraint {
382 my($spec) = @_;
e98220ab 383 return $spec if blessed($spec) && $spec->isa('Mouse::Meta::TypeConstraint');
f5ee065f 384
385 $spec =~ s/\s+//g;
386 return $TYPE{$spec};
2efc0af1 387}
388
f5ee065f 389sub find_or_parse_type_constraint {
390 my($spec) = @_;
e98220ab 391 return $spec if blessed($spec) && $spec->isa('Mouse::Meta::TypeConstraint');
9c85e9dc 392
f5ee065f 393 $spec =~ s/\s+//g;
394 return $TYPE{$spec} || do{
395 my($pos, $type) = _parse_type($spec, 0);
396 $type;
397 };
398}
321e5271 399
f5ee065f 400sub find_or_create_does_type_constraint{
401 my $type = find_or_parse_type_constriant(@_) || role_type(@_);
94593ae8 402
f5ee065f 403 if($type->{type} && $type->{type} ne 'Role'){
404 Carp::cluck("$type is not a role type");
321e5271 405 }
f5ee065f 406 return $type;
407}
408
409sub find_or_create_isa_type_constraint {
410 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 411}
412
d60c78b9 4131;
414
6feb83f1 415__END__
416
417=head1 NAME
418
5893ee36 419Mouse::Util::TypeConstraints - Type constraint system for Mouse
420
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