Checking in changes prior to tagging of version 0.95.
[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
63da74fd 7use Carp ();
8use Scalar::Util ();
9abaecfd 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 {
63da74fd 215 return Scalar::Util::blessed($_[0])
216 && Mouse::Util::does_role($_[0], $role);
89681b0b 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{
f43060b6 293 return if grep{ not defined } @_; # all things must be defined
294 my @types = sort
295 map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
f5ee065f 296
30b28db3 297 my $name = join '|', @types;
f5ee065f 298
df448257 299 # UnionType
b4d791ba 300 $TYPE{$name} ||= Mouse::Meta::TypeConstraint->new(
301 name => $name,
302 type_constraints => \@types,
b4d791ba 303 );
f5ee065f 304}
305
306# The type parser
f5ee065f 307
718b5d9b 308# param : '[' type ']' | NOTHING
309sub _parse_param {
310 my($c) = @_;
f5ee065f 311
718b5d9b 312 if($c->{spec} =~ s/^\[//){
313 my $type = _parse_type($c, 1);
f5ee065f 314
718b5d9b 315 if($c->{spec} =~ s/^\]//){
316 return $type;
321e5271 317 }
718b5d9b 318 Carp::croak("Syntax error in type: missing right square bracket in '$c->{orig}'");
319 }
57f0e313 320
718b5d9b 321 return undef;
322}
f5ee065f 323
718b5d9b 324# name : [\w.:]+
325sub _parse_name {
326 my($c, $create) = @_;
f5ee065f 327
718b5d9b 328 if($c->{spec} =~ s/\A ([\w.:]+) //xms){
329 return _find_or_create_regular_type($1, $create);
330 }
331 Carp::croak("Syntax error in type: expect type name near '$c->{spec}' in '$c->{orig}'");
332}
f5ee065f 333
718b5d9b 334# single_type : name param
335sub _parse_single_type {
336 my($c, $create) = @_;
f5ee065f 337
718b5d9b 338 my $type = _parse_name($c, $create);
339 my $param = _parse_param($c);
29376895 340
718b5d9b 341 if(defined $type){
342 if(defined $param){
343 return _find_or_create_parameterized_type($type, $param);
29376895 344 }
718b5d9b 345 else {
346 return $type;
29376895 347 }
f5ee065f 348 }
718b5d9b 349 elsif(defined $param){
350 Carp::croak("Undefined type with parameter [$param] in '$c->{orig}'");
993e62a7 351 }
352 else{
718b5d9b 353 return undef;
993e62a7 354 }
321e5271 355}
356
718b5d9b 357# type : single_type ('|' single_type)*
358sub _parse_type {
359 my($c, $create) = @_;
360
361 my $type = _parse_single_type($c, $create);
362 if($c->{spec}){ # can be an union type
363 my @types;
364 while($c->{spec} =~ s/^\|//){
365 push @types, _parse_single_type($c, $create);
366 }
367 if(@types){
368 return _find_or_create_union_type($type, @types);
369 }
370 }
371 return $type;
372}
373
f5ee065f 374
375sub find_type_constraint {
376 my($spec) = @_;
7712ea96 377 return $spec if Mouse::Util::is_a_type_constraint($spec) or not defined $spec;
f5ee065f 378
379 $spec =~ s/\s+//g;
380 return $TYPE{$spec};
2efc0af1 381}
382
35ce550a 383sub register_type_constraint {
384 my($constraint) = @_;
385 Carp::croak("No type supplied / type is not a valid type constraint")
386 unless Mouse::Util::is_a_type_constraint($constraint);
dbf2d5d4 387 return $TYPE{$constraint->name} = $constraint;
35ce550a 388}
389
f5ee065f 390sub find_or_parse_type_constraint {
391 my($spec) = @_;
7712ea96 392 return $spec if Mouse::Util::is_a_type_constraint($spec) or not defined $spec;
9c85e9dc 393
74c6212d 394 $spec =~ tr/ \t\r\n//d;
395
396 my $tc = $TYPE{$spec};
397 if(defined $tc) {
398 return $tc;
399 }
400
401 my %context = (
402 spec => $spec,
403 orig => $spec,
404 );
405 $tc = _parse_type(\%context);
406
407 if($context{spec}){
408 Carp::croak("Syntax error: extra elements '$context{spec}' in '$context{orig}'");
409 }
410
411 return $TYPE{$spec} = $tc;
f5ee065f 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.
74c6212d 416 my $tc = find_or_parse_type_constraint(@_);
417 return defined($tc) ? $tc : role_type(@_);
f5ee065f 418}
419
420sub find_or_create_isa_type_constraint {
ddbad0b1 421 # XXX: Moose does not register a new class_type, but Mouse does.
74c6212d 422 my $tc = find_or_parse_type_constraint(@_);
423 return defined($tc) ? $tc : 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
14cf9b5a 435This document describes Mouse version 0.95
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