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