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