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