Add two test file about union types
[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
f5ee065f 137 $parent = blessed($parent) ? $parent : find_or_create_isa_type_constraint($parent);
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
f5ee065f 169 while (my($from, $code) = splice @_, 0, 2) {
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;
f5ee065f 183 $COERCE{$name}->{$from} = $code;
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 $_;
f5ee065f 224 for my $type ($types, ($types->{type_constraints} ? @{$types->{type_constraints}} : ()) ) {
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",
229 $coerce_type, $type, defined($value) ? $value : 'undef',
230 $coerce_type->check($value) ? "try" : "skip";
231 }
232
233 next if !$coerce_type->check($value);
234
235 # try to coerce
b3b74cc6 236 $_ = $value;
f5ee065f 237 $_ = $COERCE{$type}->{$coerce_type}->($_); # coerce
238
239 if(_DEBUG){
240 warn sprintf "# COERCE: got %s, which is%s %s\n",
241 defined($_) ? $_ : 'undef', $types->check($_) ? '' : ' not', $types;
242 }
243
244 return $_ if $types->check($_); # check for $types, not $constraint
4188b837 245 }
246 }
4188b837 247 return $value;
248}
249
d44f0d03 250sub enum {
f5ee065f 251 my($name, %valid);
252
01904723 253 # enum ['small', 'medium', 'large']
254 if (ref($_[0]) eq 'ARRAY') {
f5ee065f 255 %valid = map{ $_ => undef } @{ $_[0] };
256 $name = sprintf '(%s)', join '|', sort @{$_[0]};
257 }
258 # enum size => 'small', 'medium', 'large'
259 else{
260 $name = shift;
261 %valid = map{ $_ => undef } @_;
262 }
263 return _create_type 'type', $name => (
264 optimized_as => sub{ defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]} },
265
266 type => 'Enum',
267 );
268}
269
270sub _find_or_create_regular_type{
271 my($spec) = @_;
272
273 return $TYPE{$spec} if exists $TYPE{$spec};
01904723 274
f5ee065f 275 my $meta = Mouse::Meta::Module::class_of($spec);
276
277 if(!$meta){
278 return;
01904723 279 }
280
f5ee065f 281 my $check;
282 my $type;
283 if($meta && $meta->isa('Mouse::Meta::Role')){
284 $check = sub{
285 return blessed($_[0]) && $_[0]->does($spec);
286 };
287 $type = 'Role';
288 }
289 else{
290 $check = sub{
291 return blessed($_[0]) && $_[0]->isa($spec);
292 };
293 $type = 'Class';
294 }
295
296 warn "#CREATE a $type type for $spec\n" if _DEBUG;
d44f0d03 297
f5ee065f 298 return $TYPE{$spec} = Mouse::Meta::TypeConstraint->new(
299 name => $spec,
300 _compiled_type_constraint => $check,
301
302 type => $type,
d44f0d03 303 );
304}
305
f5ee065f 306$TYPE{ArrayRef}{constraint_generator} = sub {
307 my($type_parameter) = @_;
308 my $check = $type_parameter->{_compiled_type_constraint};
321e5271 309
f5ee065f 310 return sub{
311 foreach my $value (@{$_}) {
312 return undef unless $check->($value);
313 }
314 return 1;
315 }
316};
317$TYPE{HashRef}{constraint_generator} = sub {
318 my($type_parameter) = @_;
319 my $check = $type_parameter->{_compiled_type_constraint};
320
321 return sub{
322 foreach my $value(values %{$_}){
323 return undef unless $check->($value);
324 }
325 return 1;
326 };
327};
2efc0af1 328
f5ee065f 329# 'Maybe' type accepts 'Any', so it requires parameters
330$TYPE{Maybe}{constraint_generator} = sub {
331 my($type_parameter) = @_;
332 my $check = $type_parameter->{_compiled_type_constraint};
2efc0af1 333
f5ee065f 334 return sub{
335 return !defined($_) || $check->($_);
336 };
337};
338
339sub _find_or_create_parameterized_type{
340 my($base, $param) = @_;
341
342 my $name = sprintf '%s[%s]', $base->name, $param->name;
343
344 $TYPE{$name} ||= do{
345 warn "#CREATE a Parameterized type for $name\n" if _DEBUG;
346
347 my $generator = $base->{constraint_generator};
348
349 if(!$generator){
350 confess("The $name constraint cannot be used, because $param doesn't subtype from a parameterizable type");
2efc0af1 351 }
f5ee065f 352
353 Mouse::Meta::TypeConstraint->new(
354 name => $name,
355 parent => $base,
356 constraint => $generator->($param),
357
358 type => 'Parameterized',
359 );
360 }
361}
362sub _find_or_create_union_type{
363 my @types = map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
364
365 my $name = join '|', map{ $_->name } @types;
366
367 $TYPE{$name} ||= do{
368 warn "# CREATE a Union type for ", Mouse::Util::english_list(@types),"\n" if _DEBUG;
369
370 my $check = sub{
371 foreach my $type(@types){
372 return 1 if $type->check($_[0]);
373 }
374 return 0;
375 };
376
377 return Mouse::Meta::TypeConstraint->new(
378 name => $name,
379 _compiled_type_constraint => $check,
380 type_constraints => \@types,
381
382 type => 'Union',
383 );
384 };
385}
386
387# The type parser
388sub _parse_type{
389 my($spec, $start) = @_;
390
391 my @list;
392 my $subtype;
393
394 my $len = length $spec;
395 my $i;
396
397 for($i = $start; $i < $len; $i++){
398 my $char = substr($spec, $i, 1);
399
400 if($char eq '['){
401 my $base = _find_or_create_regular_type( substr($spec, $start, $i - $start))
402 or return;
403
404 ($i, $subtype) = _parse_type($spec, $i+1)
405 or return;
406 $start = $i+1; # reset
407
408 push @list, _find_or_create_parameterized_type($base => $subtype);
321e5271 409 }
f5ee065f 410 elsif($char eq ']'){
411 $len = $i+1;
412 last;
321e5271 413 }
f5ee065f 414 elsif($char eq '|'){
415 my $type = _find_or_create_regular_type( substr($spec, $start, $i - $start))
416 or return;
417
418 push @list, $type;
419
420 ($i, $subtype) = _parse_type($spec, $i+1)
421 or return;
422
423 $start = $i+1; # reset
424
425 push @list, $subtype;
321e5271 426 }
427 }
f5ee065f 428 if($i - $start){
429 push @list, _find_or_create_regular_type(substr $spec, $start, $i - $start);
430 }
321e5271 431
f5ee065f 432 if(@list == 0){
433 return;
434 }
435 elsif(@list == 1){
436 return ($len, $list[0]);
993e62a7 437 }
438 else{
f5ee065f 439 return ($len, _find_or_create_union_type(@list));
993e62a7 440 }
321e5271 441}
442
f5ee065f 443
444sub find_type_constraint {
445 my($spec) = @_;
446 return $spec if blessed($spec);
447
448 $spec =~ s/\s+//g;
449 return $TYPE{$spec};
2efc0af1 450}
451
f5ee065f 452sub find_or_parse_type_constraint {
453 my($spec) = @_;
321e5271 454
f5ee065f 455 return $spec if blessed($spec);
9c85e9dc 456
f5ee065f 457 $spec =~ s/\s+//g;
458 return $TYPE{$spec} || do{
459 my($pos, $type) = _parse_type($spec, 0);
460 $type;
461 };
462}
321e5271 463
f5ee065f 464sub find_or_create_does_type_constraint{
465 my $type = find_or_parse_type_constriant(@_) || role_type(@_);
94593ae8 466
f5ee065f 467 if($type->{type} && $type->{type} ne 'Role'){
468 Carp::cluck("$type is not a role type");
321e5271 469 }
f5ee065f 470 return $type;
471}
472
473sub find_or_create_isa_type_constraint {
474 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 475}
476
d60c78b9 4771;
478
6feb83f1 479__END__
480
481=head1 NAME
482
5893ee36 483Mouse::Util::TypeConstraints - Type constraint system for Mouse
484
485=head2 SYNOPSIS
486
487 use Mouse::Util::TypeConstraints;
488
489 subtype 'Natural'
490 => as 'Int'
491 => where { $_ > 0 };
492
493 subtype 'NaturalLessThanTen'
494 => as 'Natural'
495 => where { $_ < 10 }
496 => message { "This number ($_) is not less than ten!" };
497
498 coerce 'Num'
499 => from 'Str'
500 => via { 0+$_ };
501
502 enum 'RGBColors' => qw(red green blue);
503
504 no Mouse::Util::TypeConstraints;
505
506=head1 DESCRIPTION
507
508This module provides Mouse with the ability to create custom type
509constraints to be used in attribute definition.
510
511=head2 Important Caveat
512
513This is B<NOT> a type system for Perl 5. These are type constraints,
514and they are not used by Mouse unless you tell it to. No type
515inference is performed, expressions are not typed, etc. etc. etc.
516
517A type constraint is at heart a small "check if a value is valid"
518function. A constraint can be associated with an attribute. This
519simplifies parameter validation, and makes your code clearer to read,
520because you can refer to constraints by name.
521
522=head2 Slightly Less Important Caveat
523
524It is B<always> a good idea to quote your type names.
525
526This prevents Perl from trying to execute the call as an indirect
527object call. This can be an issue when you have a subtype with the
528same name as a valid class.
529
530For instance:
531
532 subtype DateTime => as Object => where { $_->isa('DateTime') };
533
534will I<just work>, while this:
535
536 use DateTime;
537 subtype DateTime => as Object => where { $_->isa('DateTime') };
538
539will fail silently and cause many headaches. The simple way to solve
540this, as well as future proof your subtypes from classes which have
541yet to have been created, is to quote the type name:
542
543 use DateTime;
544 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
545
546=head2 Default Type Constraints
547
548This module also provides a simple hierarchy for Perl 5 types, here is
549that hierarchy represented visually.
550
551 Any
552 Item
553 Bool
554 Maybe[`a]
555 Undef
556 Defined
557 Value
558 Num
559 Int
560 Str
561 ClassName
562 RoleName
563 Ref
564 ScalarRef
565 ArrayRef[`a]
566 HashRef[`a]
567 CodeRef
568 RegexpRef
569 GlobRef
570 FileHandle
571 Object
5893ee36 572
573B<NOTE:> Any type followed by a type parameter C<[`a]> can be
574parameterized, this means you can say:
575
576 ArrayRef[Int] # an array of integers
577 HashRef[CodeRef] # a hash of str to CODE ref mappings
578 Maybe[Str] # value may be a string, may be undefined
579
580If Mouse finds a name in brackets that it does not recognize as an
581existing type, it assumes that this is a class name, for example
582C<ArrayRef[DateTime]>.
583
584B<NOTE:> Unless you parameterize a type, then it is invalid to include
585the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
586name, I<not> as a parameterization of C<ArrayRef>.
587
588B<NOTE:> The C<Undef> type constraint for the most part works
589correctly now, but edge cases may still exist, please use it
590sparingly.
591
592B<NOTE:> The C<ClassName> type constraint does a complex package
593existence check. This means that your class B<must> be loaded for this
594type constraint to pass.
595
596B<NOTE:> The C<RoleName> constraint checks a string is a I<package
597name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
598constraint checks that an I<object does> the named role.
599
600=head2 Type Constraint Naming
601
602Type name declared via this module can only contain alphanumeric
603characters, colons (:), and periods (.).
604
605Since the types created by this module are global, it is suggested
606that you namespace your types just as you would namespace your
607modules. So instead of creating a I<Color> type for your
608B<My::Graphics> module, you would call the type
609I<My::Graphics::Types::Color> instead.
610
611=head2 Use with Other Constraint Modules
612
613This module can play nicely with other constraint modules with some
614slight tweaking. The C<where> clause in types is expected to be a
615C<CODE> reference which checks it's first argument and returns a
616boolean. Since most constraint modules work in a similar way, it
617should be simple to adapt them to work with Mouse.
618
619For instance, this is how you could use it with
620L<Declare::Constraints::Simple> to declare a completely new type.
621
622 type 'HashOfArrayOfObjects',
623 {
624 where => IsHashRef(
625 -keys => HasLength,
626 -values => IsArrayRef(IsObject)
627 )
628 };
629
630Here is an example of using L<Test::Deep> and it's non-test
631related C<eq_deeply> function.
632
633 type 'ArrayOfHashOfBarsAndRandomNumbers'
634 => where {
635 eq_deeply($_,
636 array_each(subhashof({
637 bar => isa('Bar'),
638 random_number => ignore()
639 })))
640 };
6feb83f1 641
642=head1 METHODS
643
644=head2 optimized_constraints -> HashRef[CODE]
645
646Returns the simple type constraints that Mouse understands.
647
c91d12e0 648=head1 FUNCTIONS
649
650=over 4
651
1820fffe 652=item C<< subtype 'Name' => as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 653
1820fffe 654=item C<< subtype as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 655
1820fffe 656=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 657
1820fffe 658=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 659
1820fffe 660=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
661
662=back
663
664=over 4
665
666=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 667
668=back
669
5893ee36 670=head1 THANKS
671
1820fffe 672Much of this documentation was taken from C<Moose::Util::TypeConstraints>
673
674=head1 SEE ALSO
675
676L<Moose::Util::TypeConstraints>
5893ee36 677
6feb83f1 678=cut
679
680