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