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