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