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