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