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