Changelogging
[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 )],
139d92d2 17);
18
cceb0e25 19my %TYPE;
4188b837 20
5d4810c1 21sub as ($) { (as => $_[0]) }
22sub where (&) { (where => $_[0]) }
23sub message (&) { (message => $_[0]) }
24sub optimize_as (&) { (optimize_as => $_[0]) }
61a02a3a 25
73766a27 26sub from { @_ }
cd2b9201 27sub via (&) { $_[0] }
61a02a3a 28
321e5271 29BEGIN {
993e62a7 30 my %builtins = (
f5ee065f 31 Any => undef, # null check
32 Item => undef, # null check
33 Maybe => undef, # null check
73766a27 34
35 Bool => sub { $_[0] ? $_[0] eq '1' : 1 },
c91d12e0 36 Undef => sub { !defined($_[0]) },
37 Defined => sub { defined($_[0]) },
38 Value => sub { defined($_[0]) && !ref($_[0]) },
39 Num => sub { !ref($_[0]) && looks_like_number($_[0]) },
40 Int => sub { defined($_[0]) && !ref($_[0]) && $_[0] =~ /^-?[0-9]+$/ },
41 Str => sub { defined($_[0]) && !ref($_[0]) },
c91d12e0 42 Ref => sub { ref($_[0]) },
43
44 ScalarRef => sub { ref($_[0]) eq 'SCALAR' },
45 ArrayRef => sub { ref($_[0]) eq 'ARRAY' },
46 HashRef => sub { ref($_[0]) eq 'HASH' },
47 CodeRef => sub { ref($_[0]) eq 'CODE' },
48 RegexpRef => sub { ref($_[0]) eq 'Regexp' },
49 GlobRef => sub { ref($_[0]) eq 'GLOB' },
381f326a 50
51 FileHandle => sub {
c91d12e0 52 ref($_[0]) eq 'GLOB' && openhandle($_[0])
381f326a 53 or
c91d12e0 54 blessed($_[0]) && $_[0]->isa("IO::Handle")
abe4e887 55 },
381f326a 56
c91d12e0 57 Object => sub { blessed($_[0]) && blessed($_[0]) ne 'Regexp' },
73766a27 58
59 ClassName => sub { Mouse::Util::is_class_loaded($_[0]) },
d4571def 60 RoleName => sub { (Mouse::Util::find_meta($_[0]) || return 0)->isa('Mouse::Meta::Role') },
8a7f2a8a 61 );
993e62a7 62
63 while (my ($name, $code) = each %builtins) {
73766a27 64 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
3b89ea91 65 name => $name,
66 optimized => $code,
73766a27 67 );
78b13827 68 }
d3982c7e 69
deb9a0f3 70 sub optimized_constraints { # DEPRECATED
f5ee065f 71 Carp::cluck('optimized_constraints() has been deprecated');
72 return \%TYPE;
73 }
d4571def 74
993e62a7 75 my @builtins = keys %TYPE;
76 sub list_all_builtin_type_constraints { @builtins }
77
78 sub list_all_type_constraints { keys %TYPE }
381f326a 79}
d3982c7e 80
f5ee065f 81sub _create_type{
82 my $mode = shift;
83
73766a27 84 my $name;
f5ee065f 85 my %args;
73766a27 86
f5ee065f 87 if(@_ == 1 && ref $_[0]){ # @_ : { name => $name, where => ... }
88 %args = %{$_[0]};
73766a27 89 }
f5ee065f 90 elsif(@_ == 2 && ref $_[1]){ # @_ : $name => { where => ... }
73766a27 91 $name = $_[0];
f5ee065f 92 %args = %{$_[1]};
73766a27 93 }
f5ee065f 94 elsif(@_ % 2){ # @_ : $name => ( where => ... )
95 ($name, %args) = @_;
73766a27 96 }
f5ee065f 97 else{ # @_ : (name => $name, where => ...)
98 %args = @_;
73766a27 99 }
100
f5ee065f 101 if(!defined $name){
102 if(!defined($name = $args{name})){
103 $name = '__ANON__';
104 }
cd2b9201 105 }
d4571def 106
f5ee065f 107 $args{name} = $name;
b8434acc 108 my $parent;
109 if($mode eq 'subtype'){
110 $parent = delete $args{as};
111 if(!$parent){
112 $parent = delete $args{name};
113 $name = '__ANON__';
114 }
115 }
7dbebb1b 116
f5ee065f 117 my $package_defined_in = $args{package_defined_in} ||= caller(1);
d4571def 118
f5ee065f 119 my $existing = $TYPE{$name};
120 if($existing && $existing->{package_defined_in} ne $package_defined_in){
121 confess("The type constraint '$name' has already been created in "
122 . "$existing->{package_defined_in} and cannot be created again in $package_defined_in");
123 }
d4571def 124
b8434acc 125 $args{constraint} = delete $args{where} if exists $args{where};
24410e3a 126 $args{optimized} = delete $args{optimized_as} if exists $args{optimized_as};
0d9fea22 127
f5ee065f 128 my $constraint;
129 if($mode eq 'subtype'){
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{
30b28db3 305 my @types = sort{ $a cmp $b } map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
f5ee065f 306
30b28db3 307 my $name = join '|', @types;
f5ee065f 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){
29376895 365 my $type = _find_or_create_regular_type( substr $spec, $start, $i - $start );
366
367 if(defined $type){
368 push @list, $type;
369 }
370 elsif($start != 0) {
371 # RT #50421
372 # create a new class type
373 push @list, class_type( substr $spec, $start, $i - $start );
374 }
f5ee065f 375 }
321e5271 376
f5ee065f 377 if(@list == 0){
378 return;
379 }
380 elsif(@list == 1){
381 return ($len, $list[0]);
993e62a7 382 }
383 else{
f5ee065f 384 return ($len, _find_or_create_union_type(@list));
993e62a7 385 }
321e5271 386}
387
f5ee065f 388
389sub find_type_constraint {
390 my($spec) = @_;
e98220ab 391 return $spec if blessed($spec) && $spec->isa('Mouse::Meta::TypeConstraint');
f5ee065f 392
393 $spec =~ s/\s+//g;
394 return $TYPE{$spec};
2efc0af1 395}
396
f5ee065f 397sub find_or_parse_type_constraint {
398 my($spec) = @_;
e98220ab 399 return $spec if blessed($spec) && $spec->isa('Mouse::Meta::TypeConstraint');
9c85e9dc 400
f5ee065f 401 $spec =~ s/\s+//g;
402 return $TYPE{$spec} || do{
403 my($pos, $type) = _parse_type($spec, 0);
404 $type;
405 };
406}
321e5271 407
f5ee065f 408sub find_or_create_does_type_constraint{
bddbe49f 409 return find_or_parse_type_constraint(@_) || role_type(@_);
f5ee065f 410}
411
412sub find_or_create_isa_type_constraint {
413 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 414}
415
d60c78b9 4161;
417
6feb83f1 418__END__
419
420=head1 NAME
421
5893ee36 422Mouse::Util::TypeConstraints - Type constraint system for Mouse
423
a25ca8d6 424=head1 VERSION
425
9b9e4b65 426This document describes Mouse version 0.39
a25ca8d6 427
5893ee36 428=head2 SYNOPSIS
429
430 use Mouse::Util::TypeConstraints;
431
432 subtype 'Natural'
433 => as 'Int'
434 => where { $_ > 0 };
435
436 subtype 'NaturalLessThanTen'
437 => as 'Natural'
438 => where { $_ < 10 }
439 => message { "This number ($_) is not less than ten!" };
440
441 coerce 'Num'
442 => from 'Str'
443 => via { 0+$_ };
444
445 enum 'RGBColors' => qw(red green blue);
446
447 no Mouse::Util::TypeConstraints;
448
449=head1 DESCRIPTION
450
451This module provides Mouse with the ability to create custom type
452constraints to be used in attribute definition.
453
454=head2 Important Caveat
455
456This is B<NOT> a type system for Perl 5. These are type constraints,
457and they are not used by Mouse unless you tell it to. No type
458inference is performed, expressions are not typed, etc. etc. etc.
459
460A type constraint is at heart a small "check if a value is valid"
461function. A constraint can be associated with an attribute. This
462simplifies parameter validation, and makes your code clearer to read,
463because you can refer to constraints by name.
464
465=head2 Slightly Less Important Caveat
466
467It is B<always> a good idea to quote your type names.
468
469This prevents Perl from trying to execute the call as an indirect
470object call. This can be an issue when you have a subtype with the
471same name as a valid class.
472
473For instance:
474
475 subtype DateTime => as Object => where { $_->isa('DateTime') };
476
477will I<just work>, while this:
478
479 use DateTime;
480 subtype DateTime => as Object => where { $_->isa('DateTime') };
481
482will fail silently and cause many headaches. The simple way to solve
483this, as well as future proof your subtypes from classes which have
484yet to have been created, is to quote the type name:
485
486 use DateTime;
487 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
488
489=head2 Default Type Constraints
490
491This module also provides a simple hierarchy for Perl 5 types, here is
492that hierarchy represented visually.
493
494 Any
495 Item
496 Bool
497 Maybe[`a]
498 Undef
499 Defined
500 Value
501 Num
502 Int
503 Str
504 ClassName
505 RoleName
506 Ref
507 ScalarRef
508 ArrayRef[`a]
509 HashRef[`a]
510 CodeRef
511 RegexpRef
512 GlobRef
513 FileHandle
514 Object
5893ee36 515
516B<NOTE:> Any type followed by a type parameter C<[`a]> can be
517parameterized, this means you can say:
518
519 ArrayRef[Int] # an array of integers
520 HashRef[CodeRef] # a hash of str to CODE ref mappings
521 Maybe[Str] # value may be a string, may be undefined
522
523If Mouse finds a name in brackets that it does not recognize as an
524existing type, it assumes that this is a class name, for example
525C<ArrayRef[DateTime]>.
526
527B<NOTE:> Unless you parameterize a type, then it is invalid to include
528the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
529name, I<not> as a parameterization of C<ArrayRef>.
530
531B<NOTE:> The C<Undef> type constraint for the most part works
532correctly now, but edge cases may still exist, please use it
533sparingly.
534
535B<NOTE:> The C<ClassName> type constraint does a complex package
536existence check. This means that your class B<must> be loaded for this
537type constraint to pass.
538
539B<NOTE:> The C<RoleName> constraint checks a string is a I<package
540name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
541constraint checks that an I<object does> the named role.
542
543=head2 Type Constraint Naming
544
545Type name declared via this module can only contain alphanumeric
546characters, colons (:), and periods (.).
547
548Since the types created by this module are global, it is suggested
549that you namespace your types just as you would namespace your
550modules. So instead of creating a I<Color> type for your
551B<My::Graphics> module, you would call the type
552I<My::Graphics::Types::Color> instead.
553
554=head2 Use with Other Constraint Modules
555
556This module can play nicely with other constraint modules with some
557slight tweaking. The C<where> clause in types is expected to be a
558C<CODE> reference which checks it's first argument and returns a
559boolean. Since most constraint modules work in a similar way, it
560should be simple to adapt them to work with Mouse.
561
562For instance, this is how you could use it with
563L<Declare::Constraints::Simple> to declare a completely new type.
564
565 type 'HashOfArrayOfObjects',
566 {
567 where => IsHashRef(
568 -keys => HasLength,
569 -values => IsArrayRef(IsObject)
570 )
571 };
572
573Here is an example of using L<Test::Deep> and it's non-test
574related C<eq_deeply> function.
575
576 type 'ArrayOfHashOfBarsAndRandomNumbers'
577 => where {
578 eq_deeply($_,
579 array_each(subhashof({
580 bar => isa('Bar'),
581 random_number => ignore()
582 })))
583 };
6feb83f1 584
585=head1 METHODS
586
24410e3a 587=head2 C<< list_all_builtin_type_constraints -> (Names) >>
6feb83f1 588
24410e3a 589Returns the names of builtin type constraints.
590
591=head2 C<< list_all_type_constraints -> (Names) >>
592
593Returns the names of all the type constraints.
6feb83f1 594
c91d12e0 595=head1 FUNCTIONS
596
597=over 4
598
1820fffe 599=item C<< subtype 'Name' => as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 600
1820fffe 601=item C<< subtype as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 602
1820fffe 603=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 604
1820fffe 605=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 606
1820fffe 607=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
608
609=back
610
611=over 4
612
613=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 614
615=back
616
5893ee36 617=head1 THANKS
618
1820fffe 619Much of this documentation was taken from C<Moose::Util::TypeConstraints>
620
621=head1 SEE ALSO
622
623L<Moose::Util::TypeConstraints>
5893ee36 624
6feb83f1 625=cut
626
627