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