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