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