Update Makefile.PL
[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(
75 name => $name,
76 _compiled_type_constraint => $code,
f5ee065f 77 package_defined_in => __PACKAGE__,
73766a27 78 );
78b13827 79 }
d3982c7e 80
f5ee065f 81 sub optimized_constraints {
82 Carp::cluck('optimized_constraints() has been deprecated');
83 return \%TYPE;
84 }
d4571def 85
993e62a7 86 my @builtins = keys %TYPE;
87 sub list_all_builtin_type_constraints { @builtins }
88
89 sub list_all_type_constraints { keys %TYPE }
381f326a 90}
d3982c7e 91
f5ee065f 92sub _create_type{
93 my $mode = shift;
94
73766a27 95 my $name;
f5ee065f 96 my %args;
73766a27 97
f5ee065f 98 if(@_ == 1 && ref $_[0]){ # @_ : { name => $name, where => ... }
99 %args = %{$_[0]};
73766a27 100 }
f5ee065f 101 elsif(@_ == 2 && ref $_[1]){ # @_ : $name => { where => ... }
73766a27 102 $name = $_[0];
f5ee065f 103 %args = %{$_[1]};
73766a27 104 }
f5ee065f 105 elsif(@_ % 2){ # @_ : $name => ( where => ... )
106 ($name, %args) = @_;
73766a27 107 }
f5ee065f 108 else{ # @_ : (name => $name, where => ...)
109 %args = @_;
73766a27 110 }
111
f5ee065f 112 if(!defined $name){
113 if(!defined($name = $args{name})){
114 $name = '__ANON__';
115 }
cd2b9201 116 }
d4571def 117
f5ee065f 118 $args{name} = $name;
7dbebb1b 119
f5ee065f 120 my $package_defined_in = $args{package_defined_in} ||= caller(1);
d4571def 121
f5ee065f 122 my $existing = $TYPE{$name};
123 if($existing && $existing->{package_defined_in} ne $package_defined_in){
124 confess("The type constraint '$name' has already been created in "
125 . "$existing->{package_defined_in} and cannot be created again in $package_defined_in");
126 }
d4571def 127
f5ee065f 128 $args{constraint} = delete($args{where})
129 if exists $args{where};
130 $args{_compiled_type_constraint} = delete $args{optimized_as}
131 if exists $args{optimized_as};
0d9fea22 132
f5ee065f 133 my $constraint;
134 if($mode eq 'subtype'){
135 my $parent = exists($args{as}) ? delete($args{as}) : delete($args{name});
7a50b450 136
e98220ab 137 $parent = find_or_create_isa_type_constraint($parent);
f5ee065f 138 $constraint = $parent->create_child_type(%args);
7a50b450 139 }
140 else{
f5ee065f 141 $constraint = Mouse::Meta::TypeConstraint->new(%args);
73766a27 142 }
7dbebb1b 143
f5ee065f 144 return $TYPE{$name} = $constraint;
145}
7dbebb1b 146
f5ee065f 147sub type {
148 return _create_type('type', @_);
149}
d4571def 150
f5ee065f 151sub subtype {
152 return _create_type('subtype', @_);
4188b837 153}
154
139d92d2 155sub coerce {
cd2b9201 156 my $name = shift;
61a02a3a 157
f5ee065f 158 $name =~ s/\s+//g;
159 confess "Cannot find type '$name', perhaps you forgot to load it."
cceb0e25 160 unless $TYPE{$name};
61a02a3a 161
8a7f2a8a 162 unless ($COERCE{$name}) {
163 $COERCE{$name} = {};
164 $COERCE_KEYS{$name} = [];
165 }
cd2b9201 166
f5ee065f 167 my $package_defined_in = caller;
61a02a3a 168
e98220ab 169 while (my($from, $action) = splice @_, 0, 2) {
f5ee065f 170 $from =~ s/\s+//g;
171
172 confess "A coercion action already exists for '$from'"
173 if $COERCE{$name}->{$from};
174
175 my $type = find_or_parse_type_constraint($from, $package_defined_in);
176 if (!$type) {
177 confess "Could not find the type constraint ($from) to coerce from"
310ad28b 178 }
61a02a3a 179
f5ee065f 180 warn "# REGISTER COERCE $name, from $type\n" if _DEBUG;
181
cd2b9201 182 push @{ $COERCE_KEYS{$name} }, $type;
e98220ab 183 $COERCE{$name}->{$from} = $action;
61a02a3a 184 }
cd2b9201 185 return;
4188b837 186}
187
139d92d2 188sub class_type {
ecc6e3b1 189 my($name, $conf) = @_;
d9f8c878 190 if ($conf && $conf->{class}) {
191 # No, you're using this wrong
192 warn "class_type() should be class_type(ClassName). Perhaps you're looking for subtype $name => as '$conf->{class}'?";
f5ee065f 193 _create_type 'type', $name => (
194 as => $conf->{class},
195
196 type => 'Class',
197 );
a497c7d3 198 }
199 else {
f5ee065f 200 _create_type 'type', $name => (
201 optimized_as => sub { blessed($_[0]) && $_[0]->isa($name) },
202
203 type => 'Class',
d9f8c878 204 );
205 }
ecc6e3b1 206}
207
139d92d2 208sub role_type {
47f36c05 209 my($name, $conf) = @_;
f5ee065f 210 my $role = ($conf && $conf->{role}) ? $conf->{role} : $name;
211 _create_type 'type', $name => (
212 optimized_as => sub { blessed($_[0]) && does_role($_[0], $role) },
213
214 type => 'Role',
61a02a3a 215 );
47f36c05 216}
217
684db121 218# this is an original method for Mouse
4188b837 219sub typecast_constraints {
684db121 220 my($class, $pkg, $types, $value) = @_;
2efc0af1 221 Carp::croak("wrong arguments count") unless @_ == 4;
eec1bb49 222
b3b74cc6 223 local $_;
e98220ab 224 for my $type ($types->{type_constraints} ? @{$types->{type_constraints}} : $types ) {
8a7f2a8a 225 for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
f5ee065f 226
227 if(_DEBUG){
228 warn sprintf "# COERCE: from %s to %s for %s (%s)\n",
e98220ab 229 $coerce_type, $type, defined($value) ? "'$value'" : 'undef',
f5ee065f 230 $coerce_type->check($value) ? "try" : "skip";
231 }
232
233 next if !$coerce_type->check($value);
234
235 # try to coerce
b3b74cc6 236 $_ = $value;
e98220ab 237 my $coerced = $COERCE{$type}->{$coerce_type}->($value); # coerce
f5ee065f 238
239 if(_DEBUG){
240 warn sprintf "# COERCE: got %s, which is%s %s\n",
e98220ab 241 defined($coerced) ? $coerced : 'undef', $types->check($coerced) ? '' : ' not', $types;
f5ee065f 242 }
243
e98220ab 244 # check with $types, not $constraint
245 return $coerced if $types->check($coerced);
4188b837 246 }
247 }
e98220ab 248 return $value; # returns original $value
4188b837 249}
250
d44f0d03 251sub enum {
f5ee065f 252 my($name, %valid);
253
01904723 254 # enum ['small', 'medium', 'large']
255 if (ref($_[0]) eq 'ARRAY') {
f5ee065f 256 %valid = map{ $_ => undef } @{ $_[0] };
257 $name = sprintf '(%s)', join '|', sort @{$_[0]};
258 }
259 # enum size => 'small', 'medium', 'large'
260 else{
261 $name = shift;
262 %valid = map{ $_ => undef } @_;
263 }
264 return _create_type 'type', $name => (
265 optimized_as => sub{ defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]} },
266
267 type => 'Enum',
268 );
269}
270
271sub _find_or_create_regular_type{
272 my($spec) = @_;
273
274 return $TYPE{$spec} if exists $TYPE{$spec};
01904723 275
f5ee065f 276 my $meta = Mouse::Meta::Module::class_of($spec);
277
278 if(!$meta){
279 return;
01904723 280 }
281
f5ee065f 282 my $check;
283 my $type;
57f0e313 284 if($meta->isa('Mouse::Meta::Role')){
f5ee065f 285 $check = sub{
286 return blessed($_[0]) && $_[0]->does($spec);
287 };
288 $type = 'Role';
289 }
290 else{
291 $check = sub{
292 return blessed($_[0]) && $_[0]->isa($spec);
293 };
294 $type = 'Class';
295 }
296
297 warn "#CREATE a $type type for $spec\n" if _DEBUG;
d44f0d03 298
f5ee065f 299 return $TYPE{$spec} = Mouse::Meta::TypeConstraint->new(
300 name => $spec,
301 _compiled_type_constraint => $check,
302
303 type => $type,
d44f0d03 304 );
305}
306
f5ee065f 307$TYPE{ArrayRef}{constraint_generator} = sub {
308 my($type_parameter) = @_;
309 my $check = $type_parameter->{_compiled_type_constraint};
321e5271 310
f5ee065f 311 return sub{
312 foreach my $value (@{$_}) {
313 return undef unless $check->($value);
314 }
315 return 1;
316 }
317};
318$TYPE{HashRef}{constraint_generator} = sub {
319 my($type_parameter) = @_;
320 my $check = $type_parameter->{_compiled_type_constraint};
321
322 return sub{
323 foreach my $value(values %{$_}){
324 return undef unless $check->($value);
325 }
326 return 1;
327 };
328};
2efc0af1 329
f5ee065f 330# 'Maybe' type accepts 'Any', so it requires parameters
331$TYPE{Maybe}{constraint_generator} = sub {
332 my($type_parameter) = @_;
333 my $check = $type_parameter->{_compiled_type_constraint};
2efc0af1 334
f5ee065f 335 return sub{
336 return !defined($_) || $check->($_);
337 };
338};
339
340sub _find_or_create_parameterized_type{
341 my($base, $param) = @_;
342
343 my $name = sprintf '%s[%s]', $base->name, $param->name;
344
345 $TYPE{$name} ||= do{
346 warn "#CREATE a Parameterized type for $name\n" if _DEBUG;
347
348 my $generator = $base->{constraint_generator};
349
350 if(!$generator){
351 confess("The $name constraint cannot be used, because $param doesn't subtype from a parameterizable type");
2efc0af1 352 }
f5ee065f 353
354 Mouse::Meta::TypeConstraint->new(
355 name => $name,
356 parent => $base,
357 constraint => $generator->($param),
358
359 type => 'Parameterized',
360 );
361 }
362}
363sub _find_or_create_union_type{
57f0e313 364 my @types = map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
f5ee065f 365
366 my $name = join '|', map{ $_->name } @types;
367
368 $TYPE{$name} ||= do{
369 warn "# CREATE a Union type for ", Mouse::Util::english_list(@types),"\n" if _DEBUG;
370
e98220ab 371 my @checks = map{ $_->{_compiled_type_constraint} } @types;
f5ee065f 372 my $check = sub{
e98220ab 373 foreach my $c(@checks){
374 return 1 if $c->($_[0]);
f5ee065f 375 }
376 return 0;
377 };
378
379 return Mouse::Meta::TypeConstraint->new(
380 name => $name,
381 _compiled_type_constraint => $check,
382 type_constraints => \@types,
383
384 type => 'Union',
385 );
386 };
387}
388
389# The type parser
390sub _parse_type{
391 my($spec, $start) = @_;
392
393 my @list;
394 my $subtype;
395
396 my $len = length $spec;
397 my $i;
398
399 for($i = $start; $i < $len; $i++){
400 my $char = substr($spec, $i, 1);
401
402 if($char eq '['){
57f0e313 403 my $base = _find_or_create_regular_type( substr($spec, $start, $i - $start) )
f5ee065f 404 or return;
405
406 ($i, $subtype) = _parse_type($spec, $i+1)
407 or return;
408 $start = $i+1; # reset
409
410 push @list, _find_or_create_parameterized_type($base => $subtype);
321e5271 411 }
f5ee065f 412 elsif($char eq ']'){
413 $len = $i+1;
414 last;
321e5271 415 }
f5ee065f 416 elsif($char eq '|'){
57f0e313 417 my $type = _find_or_create_regular_type( substr($spec, $start, $i - $start) );
418
419 # XXX: Currently Mouse create an anonymous type for backward compatibility
420 if(!defined $type){
421 my $class = substr($spec, $start, $i - $start);
422 $type = Mouse::Meta::TypeConstraint->new(
423 name => $class,
424 _compiled_type_constraint => sub{ blessed($_[0]) && $_[0]->isa($class) },
425 );
426 }
f5ee065f 427
428 push @list, $type;
429
430 ($i, $subtype) = _parse_type($spec, $i+1)
431 or return;
432
433 $start = $i+1; # reset
434
435 push @list, $subtype;
321e5271 436 }
437 }
f5ee065f 438 if($i - $start){
439 push @list, _find_or_create_regular_type(substr $spec, $start, $i - $start);
440 }
321e5271 441
f5ee065f 442 if(@list == 0){
443 return;
444 }
445 elsif(@list == 1){
446 return ($len, $list[0]);
993e62a7 447 }
448 else{
f5ee065f 449 return ($len, _find_or_create_union_type(@list));
993e62a7 450 }
321e5271 451}
452
f5ee065f 453
454sub find_type_constraint {
455 my($spec) = @_;
e98220ab 456 return $spec if blessed($spec) && $spec->isa('Mouse::Meta::TypeConstraint');
f5ee065f 457
458 $spec =~ s/\s+//g;
459 return $TYPE{$spec};
2efc0af1 460}
461
f5ee065f 462sub find_or_parse_type_constraint {
463 my($spec) = @_;
e98220ab 464 return $spec if blessed($spec) && $spec->isa('Mouse::Meta::TypeConstraint');
9c85e9dc 465
f5ee065f 466 $spec =~ s/\s+//g;
467 return $TYPE{$spec} || do{
468 my($pos, $type) = _parse_type($spec, 0);
469 $type;
470 };
471}
321e5271 472
f5ee065f 473sub find_or_create_does_type_constraint{
474 my $type = find_or_parse_type_constriant(@_) || role_type(@_);
94593ae8 475
f5ee065f 476 if($type->{type} && $type->{type} ne 'Role'){
477 Carp::cluck("$type is not a role type");
321e5271 478 }
f5ee065f 479 return $type;
480}
481
482sub find_or_create_isa_type_constraint {
483 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 484}
485
d60c78b9 4861;
487
6feb83f1 488__END__
489
490=head1 NAME
491
5893ee36 492Mouse::Util::TypeConstraints - Type constraint system for Mouse
493
494=head2 SYNOPSIS
495
496 use Mouse::Util::TypeConstraints;
497
498 subtype 'Natural'
499 => as 'Int'
500 => where { $_ > 0 };
501
502 subtype 'NaturalLessThanTen'
503 => as 'Natural'
504 => where { $_ < 10 }
505 => message { "This number ($_) is not less than ten!" };
506
507 coerce 'Num'
508 => from 'Str'
509 => via { 0+$_ };
510
511 enum 'RGBColors' => qw(red green blue);
512
513 no Mouse::Util::TypeConstraints;
514
515=head1 DESCRIPTION
516
517This module provides Mouse with the ability to create custom type
518constraints to be used in attribute definition.
519
520=head2 Important Caveat
521
522This is B<NOT> a type system for Perl 5. These are type constraints,
523and they are not used by Mouse unless you tell it to. No type
524inference is performed, expressions are not typed, etc. etc. etc.
525
526A type constraint is at heart a small "check if a value is valid"
527function. A constraint can be associated with an attribute. This
528simplifies parameter validation, and makes your code clearer to read,
529because you can refer to constraints by name.
530
531=head2 Slightly Less Important Caveat
532
533It is B<always> a good idea to quote your type names.
534
535This prevents Perl from trying to execute the call as an indirect
536object call. This can be an issue when you have a subtype with the
537same name as a valid class.
538
539For instance:
540
541 subtype DateTime => as Object => where { $_->isa('DateTime') };
542
543will I<just work>, while this:
544
545 use DateTime;
546 subtype DateTime => as Object => where { $_->isa('DateTime') };
547
548will fail silently and cause many headaches. The simple way to solve
549this, as well as future proof your subtypes from classes which have
550yet to have been created, is to quote the type name:
551
552 use DateTime;
553 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
554
555=head2 Default Type Constraints
556
557This module also provides a simple hierarchy for Perl 5 types, here is
558that hierarchy represented visually.
559
560 Any
561 Item
562 Bool
563 Maybe[`a]
564 Undef
565 Defined
566 Value
567 Num
568 Int
569 Str
570 ClassName
571 RoleName
572 Ref
573 ScalarRef
574 ArrayRef[`a]
575 HashRef[`a]
576 CodeRef
577 RegexpRef
578 GlobRef
579 FileHandle
580 Object
5893ee36 581
582B<NOTE:> Any type followed by a type parameter C<[`a]> can be
583parameterized, this means you can say:
584
585 ArrayRef[Int] # an array of integers
586 HashRef[CodeRef] # a hash of str to CODE ref mappings
587 Maybe[Str] # value may be a string, may be undefined
588
589If Mouse finds a name in brackets that it does not recognize as an
590existing type, it assumes that this is a class name, for example
591C<ArrayRef[DateTime]>.
592
593B<NOTE:> Unless you parameterize a type, then it is invalid to include
594the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
595name, I<not> as a parameterization of C<ArrayRef>.
596
597B<NOTE:> The C<Undef> type constraint for the most part works
598correctly now, but edge cases may still exist, please use it
599sparingly.
600
601B<NOTE:> The C<ClassName> type constraint does a complex package
602existence check. This means that your class B<must> be loaded for this
603type constraint to pass.
604
605B<NOTE:> The C<RoleName> constraint checks a string is a I<package
606name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
607constraint checks that an I<object does> the named role.
608
609=head2 Type Constraint Naming
610
611Type name declared via this module can only contain alphanumeric
612characters, colons (:), and periods (.).
613
614Since the types created by this module are global, it is suggested
615that you namespace your types just as you would namespace your
616modules. So instead of creating a I<Color> type for your
617B<My::Graphics> module, you would call the type
618I<My::Graphics::Types::Color> instead.
619
620=head2 Use with Other Constraint Modules
621
622This module can play nicely with other constraint modules with some
623slight tweaking. The C<where> clause in types is expected to be a
624C<CODE> reference which checks it's first argument and returns a
625boolean. Since most constraint modules work in a similar way, it
626should be simple to adapt them to work with Mouse.
627
628For instance, this is how you could use it with
629L<Declare::Constraints::Simple> to declare a completely new type.
630
631 type 'HashOfArrayOfObjects',
632 {
633 where => IsHashRef(
634 -keys => HasLength,
635 -values => IsArrayRef(IsObject)
636 )
637 };
638
639Here is an example of using L<Test::Deep> and it's non-test
640related C<eq_deeply> function.
641
642 type 'ArrayOfHashOfBarsAndRandomNumbers'
643 => where {
644 eq_deeply($_,
645 array_each(subhashof({
646 bar => isa('Bar'),
647 random_number => ignore()
648 })))
649 };
6feb83f1 650
651=head1 METHODS
652
653=head2 optimized_constraints -> HashRef[CODE]
654
655Returns the simple type constraints that Mouse understands.
656
c91d12e0 657=head1 FUNCTIONS
658
659=over 4
660
1820fffe 661=item C<< subtype 'Name' => as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 662
1820fffe 663=item C<< subtype as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 664
1820fffe 665=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 666
1820fffe 667=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 668
1820fffe 669=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
670
671=back
672
673=over 4
674
675=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 676
677=back
678
5893ee36 679=head1 THANKS
680
1820fffe 681Much of this documentation was taken from C<Moose::Util::TypeConstraints>
682
683=head1 SEE ALSO
684
685L<Moose::Util::TypeConstraints>
5893ee36 686
6feb83f1 687=cut
688
689