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