Improve enum to accept "enum($name, $arrayref)" construction
[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',
e3540312 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
f152b099 187 if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){
188 $name = shift;
f5ee065f 189 }
f152b099 190
191 %valid = map{ $_ => undef } (@_ == 1 && ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_);
192
f5ee065f 193 return _create_type 'type', $name => (
194 optimized_as => sub{ defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]} },
195
196 type => 'Enum',
197 );
198}
199
200sub _find_or_create_regular_type{
201 my($spec) = @_;
202
203 return $TYPE{$spec} if exists $TYPE{$spec};
01904723 204
337e3b0c 205 my $meta = Mouse::Util::get_metaclass_by_name($spec)
206 or return undef;
01904723 207
f48920c1 208 if(Mouse::Util::is_a_metarole($meta)){
1d5ecd5f 209 return role_type($spec);
f5ee065f 210 }
211 else{
1d5ecd5f 212 return class_type($spec);
f5ee065f 213 }
d44f0d03 214}
215
619338ac 216$TYPE{ArrayRef}{constraint_generator} = \&_parameterize_ArrayRef_for;
217$TYPE{HashRef}{constraint_generator} = \&_parameterize_HashRef_for;
218$TYPE{Maybe}{constraint_generator} = \&_parameterize_Maybe_for;
f5ee065f 219
220sub _find_or_create_parameterized_type{
221 my($base, $param) = @_;
222
223 my $name = sprintf '%s[%s]', $base->name, $param->name;
224
b4d791ba 225 $TYPE{$name} ||= $base->parameterize($param, $name);
f5ee065f 226}
b4d791ba 227
f5ee065f 228sub _find_or_create_union_type{
a2f1294a 229 my @types = sort map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
f5ee065f 230
30b28db3 231 my $name = join '|', @types;
f5ee065f 232
b4d791ba 233 $TYPE{$name} ||= Mouse::Meta::TypeConstraint->new(
234 name => $name,
235 type_constraints => \@types,
f5ee065f 236
b4d791ba 237 type => 'Union',
238 );
f5ee065f 239}
240
241# The type parser
242sub _parse_type{
243 my($spec, $start) = @_;
244
245 my @list;
246 my $subtype;
247
248 my $len = length $spec;
249 my $i;
250
251 for($i = $start; $i < $len; $i++){
252 my $char = substr($spec, $i, 1);
253
254 if($char eq '['){
57f0e313 255 my $base = _find_or_create_regular_type( substr($spec, $start, $i - $start) )
f5ee065f 256 or return;
257
258 ($i, $subtype) = _parse_type($spec, $i+1)
259 or return;
260 $start = $i+1; # reset
261
262 push @list, _find_or_create_parameterized_type($base => $subtype);
321e5271 263 }
f5ee065f 264 elsif($char eq ']'){
265 $len = $i+1;
266 last;
321e5271 267 }
f5ee065f 268 elsif($char eq '|'){
57f0e313 269 my $type = _find_or_create_regular_type( substr($spec, $start, $i - $start) );
270
57f0e313 271 if(!defined $type){
3b89ea91 272 # XXX: Mouse creates a new class type, but Moose does not.
273 $type = class_type( substr($spec, $start, $i - $start) );
57f0e313 274 }
f5ee065f 275
276 push @list, $type;
277
278 ($i, $subtype) = _parse_type($spec, $i+1)
279 or return;
280
281 $start = $i+1; # reset
282
283 push @list, $subtype;
321e5271 284 }
285 }
f5ee065f 286 if($i - $start){
29376895 287 my $type = _find_or_create_regular_type( substr $spec, $start, $i - $start );
288
289 if(defined $type){
290 push @list, $type;
291 }
292 elsif($start != 0) {
293 # RT #50421
294 # create a new class type
295 push @list, class_type( substr $spec, $start, $i - $start );
296 }
f5ee065f 297 }
321e5271 298
f5ee065f 299 if(@list == 0){
300 return;
301 }
302 elsif(@list == 1){
303 return ($len, $list[0]);
993e62a7 304 }
305 else{
f5ee065f 306 return ($len, _find_or_create_union_type(@list));
993e62a7 307 }
321e5271 308}
309
f5ee065f 310
311sub find_type_constraint {
312 my($spec) = @_;
f48920c1 313 return $spec if Mouse::Util::is_a_type_constraint($spec);
f5ee065f 314
315 $spec =~ s/\s+//g;
316 return $TYPE{$spec};
2efc0af1 317}
318
f5ee065f 319sub find_or_parse_type_constraint {
320 my($spec) = @_;
f48920c1 321 return $spec if Mouse::Util::is_a_type_constraint($spec);
9c85e9dc 322
f5ee065f 323 $spec =~ s/\s+//g;
324 return $TYPE{$spec} || do{
325 my($pos, $type) = _parse_type($spec, 0);
326 $type;
327 };
328}
321e5271 329
f5ee065f 330sub find_or_create_does_type_constraint{
ddbad0b1 331 # XXX: Moose does not register a new role_type, but Mouse does.
bddbe49f 332 return find_or_parse_type_constraint(@_) || role_type(@_);
f5ee065f 333}
334
335sub find_or_create_isa_type_constraint {
ddbad0b1 336 # XXX: Moose does not register a new class_type, but Mouse does.
f5ee065f 337 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 338}
339
d60c78b9 3401;
6feb83f1 341__END__
342
343=head1 NAME
344
5893ee36 345Mouse::Util::TypeConstraints - Type constraint system for Mouse
346
a25ca8d6 347=head1 VERSION
348
4819ed36 349This document describes Mouse version 0.43
a25ca8d6 350
5893ee36 351=head2 SYNOPSIS
352
353 use Mouse::Util::TypeConstraints;
354
355 subtype 'Natural'
356 => as 'Int'
357 => where { $_ > 0 };
358
359 subtype 'NaturalLessThanTen'
360 => as 'Natural'
361 => where { $_ < 10 }
362 => message { "This number ($_) is not less than ten!" };
363
364 coerce 'Num'
365 => from 'Str'
366 => via { 0+$_ };
367
368 enum 'RGBColors' => qw(red green blue);
369
370 no Mouse::Util::TypeConstraints;
371
372=head1 DESCRIPTION
373
374This module provides Mouse with the ability to create custom type
375constraints to be used in attribute definition.
376
377=head2 Important Caveat
378
379This is B<NOT> a type system for Perl 5. These are type constraints,
380and they are not used by Mouse unless you tell it to. No type
381inference is performed, expressions are not typed, etc. etc. etc.
382
383A type constraint is at heart a small "check if a value is valid"
384function. A constraint can be associated with an attribute. This
385simplifies parameter validation, and makes your code clearer to read,
386because you can refer to constraints by name.
387
388=head2 Slightly Less Important Caveat
389
390It is B<always> a good idea to quote your type names.
391
392This prevents Perl from trying to execute the call as an indirect
393object call. This can be an issue when you have a subtype with the
394same name as a valid class.
395
396For instance:
397
398 subtype DateTime => as Object => where { $_->isa('DateTime') };
399
400will I<just work>, while this:
401
402 use DateTime;
403 subtype DateTime => as Object => where { $_->isa('DateTime') };
404
405will fail silently and cause many headaches. The simple way to solve
406this, as well as future proof your subtypes from classes which have
407yet to have been created, is to quote the type name:
408
409 use DateTime;
410 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
411
412=head2 Default Type Constraints
413
414This module also provides a simple hierarchy for Perl 5 types, here is
415that hierarchy represented visually.
416
dba2e142 417 Any
5893ee36 418 Item
419 Bool
420 Maybe[`a]
421 Undef
422 Defined
423 Value
5893ee36 424 Str
dba2e142 425 Num
426 Int
427 ClassName
428 RoleName
5893ee36 429 Ref
430 ScalarRef
431 ArrayRef[`a]
432 HashRef[`a]
433 CodeRef
434 RegexpRef
435 GlobRef
dba2e142 436 FileHandle
5893ee36 437 Object
5893ee36 438
439B<NOTE:> Any type followed by a type parameter C<[`a]> can be
440parameterized, this means you can say:
441
442 ArrayRef[Int] # an array of integers
443 HashRef[CodeRef] # a hash of str to CODE ref mappings
444 Maybe[Str] # value may be a string, may be undefined
445
446If Mouse finds a name in brackets that it does not recognize as an
447existing type, it assumes that this is a class name, for example
448C<ArrayRef[DateTime]>.
449
450B<NOTE:> Unless you parameterize a type, then it is invalid to include
451the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
452name, I<not> as a parameterization of C<ArrayRef>.
453
454B<NOTE:> The C<Undef> type constraint for the most part works
455correctly now, but edge cases may still exist, please use it
456sparingly.
457
458B<NOTE:> The C<ClassName> type constraint does a complex package
459existence check. This means that your class B<must> be loaded for this
460type constraint to pass.
461
462B<NOTE:> The C<RoleName> constraint checks a string is a I<package
463name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
464constraint checks that an I<object does> the named role.
465
466=head2 Type Constraint Naming
467
468Type name declared via this module can only contain alphanumeric
469characters, colons (:), and periods (.).
470
471Since the types created by this module are global, it is suggested
472that you namespace your types just as you would namespace your
473modules. So instead of creating a I<Color> type for your
474B<My::Graphics> module, you would call the type
475I<My::Graphics::Types::Color> instead.
476
477=head2 Use with Other Constraint Modules
478
479This module can play nicely with other constraint modules with some
480slight tweaking. The C<where> clause in types is expected to be a
481C<CODE> reference which checks it's first argument and returns a
482boolean. Since most constraint modules work in a similar way, it
483should be simple to adapt them to work with Mouse.
484
485For instance, this is how you could use it with
486L<Declare::Constraints::Simple> to declare a completely new type.
487
488 type 'HashOfArrayOfObjects',
489 {
490 where => IsHashRef(
491 -keys => HasLength,
492 -values => IsArrayRef(IsObject)
493 )
494 };
495
496Here is an example of using L<Test::Deep> and it's non-test
497related C<eq_deeply> function.
498
499 type 'ArrayOfHashOfBarsAndRandomNumbers'
500 => where {
501 eq_deeply($_,
502 array_each(subhashof({
503 bar => isa('Bar'),
504 random_number => ignore()
505 })))
506 };
6feb83f1 507
508=head1 METHODS
509
24410e3a 510=head2 C<< list_all_builtin_type_constraints -> (Names) >>
6feb83f1 511
24410e3a 512Returns the names of builtin type constraints.
513
514=head2 C<< list_all_type_constraints -> (Names) >>
515
516Returns the names of all the type constraints.
6feb83f1 517
c91d12e0 518=head1 FUNCTIONS
519
520=over 4
521
1820fffe 522=item C<< subtype 'Name' => as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 523
1820fffe 524=item C<< subtype as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 525
1820fffe 526=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 527
1820fffe 528=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 529
1820fffe 530=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
531
532=back
533
534=over 4
535
536=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 537
538=back
539
5893ee36 540=head1 THANKS
541
1820fffe 542Much of this documentation was taken from C<Moose::Util::TypeConstraints>
543
544=head1 SEE ALSO
545
546L<Moose::Util::TypeConstraints>
5893ee36 547
6feb83f1 548=cut
549
550