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 )],
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){
367 push @list, _find_or_create_regular_type(substr $spec, $start, $i - $start);
368 }
321e5271 369
f5ee065f 370 if(@list == 0){
371 return;
372 }
373 elsif(@list == 1){
374 return ($len, $list[0]);
993e62a7 375 }
376 else{
f5ee065f 377 return ($len, _find_or_create_union_type(@list));
993e62a7 378 }
321e5271 379}
380
f5ee065f 381
382sub find_type_constraint {
383 my($spec) = @_;
e98220ab 384 return $spec if blessed($spec) && $spec->isa('Mouse::Meta::TypeConstraint');
f5ee065f 385
386 $spec =~ s/\s+//g;
387 return $TYPE{$spec};
2efc0af1 388}
389
f5ee065f 390sub find_or_parse_type_constraint {
391 my($spec) = @_;
e98220ab 392 return $spec if blessed($spec) && $spec->isa('Mouse::Meta::TypeConstraint');
9c85e9dc 393
f5ee065f 394 $spec =~ s/\s+//g;
395 return $TYPE{$spec} || do{
396 my($pos, $type) = _parse_type($spec, 0);
397 $type;
398 };
399}
321e5271 400
f5ee065f 401sub find_or_create_does_type_constraint{
bddbe49f 402 return find_or_parse_type_constraint(@_) || role_type(@_);
f5ee065f 403}
404
405sub find_or_create_isa_type_constraint {
406 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 407}
408
d60c78b9 4091;
410
6feb83f1 411__END__
412
413=head1 NAME
414
5893ee36 415Mouse::Util::TypeConstraints - Type constraint system for Mouse
416
417=head2 SYNOPSIS
418
419 use Mouse::Util::TypeConstraints;
420
421 subtype 'Natural'
422 => as 'Int'
423 => where { $_ > 0 };
424
425 subtype 'NaturalLessThanTen'
426 => as 'Natural'
427 => where { $_ < 10 }
428 => message { "This number ($_) is not less than ten!" };
429
430 coerce 'Num'
431 => from 'Str'
432 => via { 0+$_ };
433
434 enum 'RGBColors' => qw(red green blue);
435
436 no Mouse::Util::TypeConstraints;
437
438=head1 DESCRIPTION
439
440This module provides Mouse with the ability to create custom type
441constraints to be used in attribute definition.
442
443=head2 Important Caveat
444
445This is B<NOT> a type system for Perl 5. These are type constraints,
446and they are not used by Mouse unless you tell it to. No type
447inference is performed, expressions are not typed, etc. etc. etc.
448
449A type constraint is at heart a small "check if a value is valid"
450function. A constraint can be associated with an attribute. This
451simplifies parameter validation, and makes your code clearer to read,
452because you can refer to constraints by name.
453
454=head2 Slightly Less Important Caveat
455
456It is B<always> a good idea to quote your type names.
457
458This prevents Perl from trying to execute the call as an indirect
459object call. This can be an issue when you have a subtype with the
460same name as a valid class.
461
462For instance:
463
464 subtype DateTime => as Object => where { $_->isa('DateTime') };
465
466will I<just work>, while this:
467
468 use DateTime;
469 subtype DateTime => as Object => where { $_->isa('DateTime') };
470
471will fail silently and cause many headaches. The simple way to solve
472this, as well as future proof your subtypes from classes which have
473yet to have been created, is to quote the type name:
474
475 use DateTime;
476 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
477
478=head2 Default Type Constraints
479
480This module also provides a simple hierarchy for Perl 5 types, here is
481that hierarchy represented visually.
482
483 Any
484 Item
485 Bool
486 Maybe[`a]
487 Undef
488 Defined
489 Value
490 Num
491 Int
492 Str
493 ClassName
494 RoleName
495 Ref
496 ScalarRef
497 ArrayRef[`a]
498 HashRef[`a]
499 CodeRef
500 RegexpRef
501 GlobRef
502 FileHandle
503 Object
5893ee36 504
505B<NOTE:> Any type followed by a type parameter C<[`a]> can be
506parameterized, this means you can say:
507
508 ArrayRef[Int] # an array of integers
509 HashRef[CodeRef] # a hash of str to CODE ref mappings
510 Maybe[Str] # value may be a string, may be undefined
511
512If Mouse finds a name in brackets that it does not recognize as an
513existing type, it assumes that this is a class name, for example
514C<ArrayRef[DateTime]>.
515
516B<NOTE:> Unless you parameterize a type, then it is invalid to include
517the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
518name, I<not> as a parameterization of C<ArrayRef>.
519
520B<NOTE:> The C<Undef> type constraint for the most part works
521correctly now, but edge cases may still exist, please use it
522sparingly.
523
524B<NOTE:> The C<ClassName> type constraint does a complex package
525existence check. This means that your class B<must> be loaded for this
526type constraint to pass.
527
528B<NOTE:> The C<RoleName> constraint checks a string is a I<package
529name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
530constraint checks that an I<object does> the named role.
531
532=head2 Type Constraint Naming
533
534Type name declared via this module can only contain alphanumeric
535characters, colons (:), and periods (.).
536
537Since the types created by this module are global, it is suggested
538that you namespace your types just as you would namespace your
539modules. So instead of creating a I<Color> type for your
540B<My::Graphics> module, you would call the type
541I<My::Graphics::Types::Color> instead.
542
543=head2 Use with Other Constraint Modules
544
545This module can play nicely with other constraint modules with some
546slight tweaking. The C<where> clause in types is expected to be a
547C<CODE> reference which checks it's first argument and returns a
548boolean. Since most constraint modules work in a similar way, it
549should be simple to adapt them to work with Mouse.
550
551For instance, this is how you could use it with
552L<Declare::Constraints::Simple> to declare a completely new type.
553
554 type 'HashOfArrayOfObjects',
555 {
556 where => IsHashRef(
557 -keys => HasLength,
558 -values => IsArrayRef(IsObject)
559 )
560 };
561
562Here is an example of using L<Test::Deep> and it's non-test
563related C<eq_deeply> function.
564
565 type 'ArrayOfHashOfBarsAndRandomNumbers'
566 => where {
567 eq_deeply($_,
568 array_each(subhashof({
569 bar => isa('Bar'),
570 random_number => ignore()
571 })))
572 };
6feb83f1 573
574=head1 METHODS
575
24410e3a 576=head2 C<< list_all_builtin_type_constraints -> (Names) >>
6feb83f1 577
24410e3a 578Returns the names of builtin type constraints.
579
580=head2 C<< list_all_type_constraints -> (Names) >>
581
582Returns the names of all the type constraints.
6feb83f1 583
c91d12e0 584=head1 FUNCTIONS
585
586=over 4
587
1820fffe 588=item C<< subtype 'Name' => as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 589
1820fffe 590=item C<< subtype as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 591
1820fffe 592=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 593
1820fffe 594=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 595
1820fffe 596=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
597
598=back
599
600=over 4
601
602=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 603
604=back
605
5893ee36 606=head1 THANKS
607
1820fffe 608Much of this documentation was taken from C<Moose::Util::TypeConstraints>
609
610=head1 SEE ALSO
611
612L<Moose::Util::TypeConstraints>
5893ee36 613
6feb83f1 614=cut
615
616