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