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