Checking in changes prior to tagging of version 0.40_05. 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;
345
6feb83f1 346__END__
347
348=head1 NAME
349
5893ee36 350Mouse::Util::TypeConstraints - Type constraint system for Mouse
351
a25ca8d6 352=head1 VERSION
353
6e168432 354This document describes Mouse version 0.40_05
a25ca8d6 355
5893ee36 356=head2 SYNOPSIS
357
358 use Mouse::Util::TypeConstraints;
359
360 subtype 'Natural'
361 => as 'Int'
362 => where { $_ > 0 };
363
364 subtype 'NaturalLessThanTen'
365 => as 'Natural'
366 => where { $_ < 10 }
367 => message { "This number ($_) is not less than ten!" };
368
369 coerce 'Num'
370 => from 'Str'
371 => via { 0+$_ };
372
373 enum 'RGBColors' => qw(red green blue);
374
375 no Mouse::Util::TypeConstraints;
376
377=head1 DESCRIPTION
378
379This module provides Mouse with the ability to create custom type
380constraints to be used in attribute definition.
381
382=head2 Important Caveat
383
384This is B<NOT> a type system for Perl 5. These are type constraints,
385and they are not used by Mouse unless you tell it to. No type
386inference is performed, expressions are not typed, etc. etc. etc.
387
388A type constraint is at heart a small "check if a value is valid"
389function. A constraint can be associated with an attribute. This
390simplifies parameter validation, and makes your code clearer to read,
391because you can refer to constraints by name.
392
393=head2 Slightly Less Important Caveat
394
395It is B<always> a good idea to quote your type names.
396
397This prevents Perl from trying to execute the call as an indirect
398object call. This can be an issue when you have a subtype with the
399same name as a valid class.
400
401For instance:
402
403 subtype DateTime => as Object => where { $_->isa('DateTime') };
404
405will I<just work>, while this:
406
407 use DateTime;
408 subtype DateTime => as Object => where { $_->isa('DateTime') };
409
410will fail silently and cause many headaches. The simple way to solve
411this, as well as future proof your subtypes from classes which have
412yet to have been created, is to quote the type name:
413
414 use DateTime;
415 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
416
417=head2 Default Type Constraints
418
419This module also provides a simple hierarchy for Perl 5 types, here is
420that hierarchy represented visually.
421
422 Any
423 Item
424 Bool
425 Maybe[`a]
426 Undef
427 Defined
428 Value
429 Num
430 Int
431 Str
432 ClassName
433 RoleName
434 Ref
435 ScalarRef
436 ArrayRef[`a]
437 HashRef[`a]
438 CodeRef
439 RegexpRef
440 GlobRef
441 FileHandle
442 Object
5893ee36 443
444B<NOTE:> Any type followed by a type parameter C<[`a]> can be
445parameterized, this means you can say:
446
447 ArrayRef[Int] # an array of integers
448 HashRef[CodeRef] # a hash of str to CODE ref mappings
449 Maybe[Str] # value may be a string, may be undefined
450
451If Mouse finds a name in brackets that it does not recognize as an
452existing type, it assumes that this is a class name, for example
453C<ArrayRef[DateTime]>.
454
455B<NOTE:> Unless you parameterize a type, then it is invalid to include
456the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
457name, I<not> as a parameterization of C<ArrayRef>.
458
459B<NOTE:> The C<Undef> type constraint for the most part works
460correctly now, but edge cases may still exist, please use it
461sparingly.
462
463B<NOTE:> The C<ClassName> type constraint does a complex package
464existence check. This means that your class B<must> be loaded for this
465type constraint to pass.
466
467B<NOTE:> The C<RoleName> constraint checks a string is a I<package
468name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
469constraint checks that an I<object does> the named role.
470
471=head2 Type Constraint Naming
472
473Type name declared via this module can only contain alphanumeric
474characters, colons (:), and periods (.).
475
476Since the types created by this module are global, it is suggested
477that you namespace your types just as you would namespace your
478modules. So instead of creating a I<Color> type for your
479B<My::Graphics> module, you would call the type
480I<My::Graphics::Types::Color> instead.
481
482=head2 Use with Other Constraint Modules
483
484This module can play nicely with other constraint modules with some
485slight tweaking. The C<where> clause in types is expected to be a
486C<CODE> reference which checks it's first argument and returns a
487boolean. Since most constraint modules work in a similar way, it
488should be simple to adapt them to work with Mouse.
489
490For instance, this is how you could use it with
491L<Declare::Constraints::Simple> to declare a completely new type.
492
493 type 'HashOfArrayOfObjects',
494 {
495 where => IsHashRef(
496 -keys => HasLength,
497 -values => IsArrayRef(IsObject)
498 )
499 };
500
501Here is an example of using L<Test::Deep> and it's non-test
502related C<eq_deeply> function.
503
504 type 'ArrayOfHashOfBarsAndRandomNumbers'
505 => where {
506 eq_deeply($_,
507 array_each(subhashof({
508 bar => isa('Bar'),
509 random_number => ignore()
510 })))
511 };
6feb83f1 512
513=head1 METHODS
514
24410e3a 515=head2 C<< list_all_builtin_type_constraints -> (Names) >>
6feb83f1 516
24410e3a 517Returns the names of builtin type constraints.
518
519=head2 C<< list_all_type_constraints -> (Names) >>
520
521Returns the names of all the type constraints.
6feb83f1 522
c91d12e0 523=head1 FUNCTIONS
524
525=over 4
526
1820fffe 527=item C<< subtype 'Name' => as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 528
1820fffe 529=item C<< subtype as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 530
1820fffe 531=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 532
1820fffe 533=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 534
1820fffe 535=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
536
537=back
538
539=over 4
540
541=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 542
543=back
544
5893ee36 545=head1 THANKS
546
1820fffe 547Much of this documentation was taken from C<Moose::Util::TypeConstraints>
548
549=head1 SEE ALSO
550
551L<Moose::Util::TypeConstraints>
5893ee36 552
6feb83f1 553=cut
554
555