Ensure the version of Module::Install::XSUtil
[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
266 $TYPE{$name} ||= do{
f5ee065f 267 my $generator = $base->{constraint_generator};
268
269 if(!$generator){
270 confess("The $name constraint cannot be used, because $param doesn't subtype from a parameterizable type");
2efc0af1 271 }
f5ee065f 272
273 Mouse::Meta::TypeConstraint->new(
274 name => $name,
275 parent => $base,
276 constraint => $generator->($param),
277
278 type => 'Parameterized',
279 );
280 }
281}
282sub _find_or_create_union_type{
30b28db3 283 my @types = sort{ $a cmp $b } map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
f5ee065f 284
30b28db3 285 my $name = join '|', @types;
f5ee065f 286
287 $TYPE{$name} ||= do{
f5ee065f 288 return Mouse::Meta::TypeConstraint->new(
3b89ea91 289 name => $name,
290 type_constraints => \@types,
f5ee065f 291
3b89ea91 292 type => 'Union',
f5ee065f 293 );
294 };
295}
296
297# The type parser
298sub _parse_type{
299 my($spec, $start) = @_;
300
301 my @list;
302 my $subtype;
303
304 my $len = length $spec;
305 my $i;
306
307 for($i = $start; $i < $len; $i++){
308 my $char = substr($spec, $i, 1);
309
310 if($char eq '['){
57f0e313 311 my $base = _find_or_create_regular_type( substr($spec, $start, $i - $start) )
f5ee065f 312 or return;
313
314 ($i, $subtype) = _parse_type($spec, $i+1)
315 or return;
316 $start = $i+1; # reset
317
318 push @list, _find_or_create_parameterized_type($base => $subtype);
321e5271 319 }
f5ee065f 320 elsif($char eq ']'){
321 $len = $i+1;
322 last;
321e5271 323 }
f5ee065f 324 elsif($char eq '|'){
57f0e313 325 my $type = _find_or_create_regular_type( substr($spec, $start, $i - $start) );
326
57f0e313 327 if(!defined $type){
3b89ea91 328 # XXX: Mouse creates a new class type, but Moose does not.
329 $type = class_type( substr($spec, $start, $i - $start) );
57f0e313 330 }
f5ee065f 331
332 push @list, $type;
333
334 ($i, $subtype) = _parse_type($spec, $i+1)
335 or return;
336
337 $start = $i+1; # reset
338
339 push @list, $subtype;
321e5271 340 }
341 }
f5ee065f 342 if($i - $start){
29376895 343 my $type = _find_or_create_regular_type( substr $spec, $start, $i - $start );
344
345 if(defined $type){
346 push @list, $type;
347 }
348 elsif($start != 0) {
349 # RT #50421
350 # create a new class type
351 push @list, class_type( substr $spec, $start, $i - $start );
352 }
f5ee065f 353 }
321e5271 354
f5ee065f 355 if(@list == 0){
356 return;
357 }
358 elsif(@list == 1){
359 return ($len, $list[0]);
993e62a7 360 }
361 else{
f5ee065f 362 return ($len, _find_or_create_union_type(@list));
993e62a7 363 }
321e5271 364}
365
f5ee065f 366
367sub find_type_constraint {
368 my($spec) = @_;
745220df 369 return $spec if _is_a_type_constraint($spec);
f5ee065f 370
371 $spec =~ s/\s+//g;
372 return $TYPE{$spec};
2efc0af1 373}
374
f5ee065f 375sub find_or_parse_type_constraint {
376 my($spec) = @_;
745220df 377 return $spec if _is_a_type_constraint($spec);
9c85e9dc 378
f5ee065f 379 $spec =~ s/\s+//g;
380 return $TYPE{$spec} || do{
381 my($pos, $type) = _parse_type($spec, 0);
382 $type;
383 };
384}
321e5271 385
f5ee065f 386sub find_or_create_does_type_constraint{
ddbad0b1 387 # XXX: Moose does not register a new role_type, but Mouse does.
bddbe49f 388 return find_or_parse_type_constraint(@_) || role_type(@_);
f5ee065f 389}
390
391sub find_or_create_isa_type_constraint {
ddbad0b1 392 # XXX: Moose does not register a new class_type, but Mouse does.
f5ee065f 393 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 394}
395
d60c78b9 3961;
397
6feb83f1 398__END__
399
400=head1 NAME
401
5893ee36 402Mouse::Util::TypeConstraints - Type constraint system for Mouse
403
a25ca8d6 404=head1 VERSION
405
065f79e7 406This document describes Mouse version 0.40_02
a25ca8d6 407
5893ee36 408=head2 SYNOPSIS
409
410 use Mouse::Util::TypeConstraints;
411
412 subtype 'Natural'
413 => as 'Int'
414 => where { $_ > 0 };
415
416 subtype 'NaturalLessThanTen'
417 => as 'Natural'
418 => where { $_ < 10 }
419 => message { "This number ($_) is not less than ten!" };
420
421 coerce 'Num'
422 => from 'Str'
423 => via { 0+$_ };
424
425 enum 'RGBColors' => qw(red green blue);
426
427 no Mouse::Util::TypeConstraints;
428
429=head1 DESCRIPTION
430
431This module provides Mouse with the ability to create custom type
432constraints to be used in attribute definition.
433
434=head2 Important Caveat
435
436This is B<NOT> a type system for Perl 5. These are type constraints,
437and they are not used by Mouse unless you tell it to. No type
438inference is performed, expressions are not typed, etc. etc. etc.
439
440A type constraint is at heart a small "check if a value is valid"
441function. A constraint can be associated with an attribute. This
442simplifies parameter validation, and makes your code clearer to read,
443because you can refer to constraints by name.
444
445=head2 Slightly Less Important Caveat
446
447It is B<always> a good idea to quote your type names.
448
449This prevents Perl from trying to execute the call as an indirect
450object call. This can be an issue when you have a subtype with the
451same name as a valid class.
452
453For instance:
454
455 subtype DateTime => as Object => where { $_->isa('DateTime') };
456
457will I<just work>, while this:
458
459 use DateTime;
460 subtype DateTime => as Object => where { $_->isa('DateTime') };
461
462will fail silently and cause many headaches. The simple way to solve
463this, as well as future proof your subtypes from classes which have
464yet to have been created, is to quote the type name:
465
466 use DateTime;
467 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
468
469=head2 Default Type Constraints
470
471This module also provides a simple hierarchy for Perl 5 types, here is
472that hierarchy represented visually.
473
474 Any
475 Item
476 Bool
477 Maybe[`a]
478 Undef
479 Defined
480 Value
481 Num
482 Int
483 Str
484 ClassName
485 RoleName
486 Ref
487 ScalarRef
488 ArrayRef[`a]
489 HashRef[`a]
490 CodeRef
491 RegexpRef
492 GlobRef
493 FileHandle
494 Object
5893ee36 495
496B<NOTE:> Any type followed by a type parameter C<[`a]> can be
497parameterized, this means you can say:
498
499 ArrayRef[Int] # an array of integers
500 HashRef[CodeRef] # a hash of str to CODE ref mappings
501 Maybe[Str] # value may be a string, may be undefined
502
503If Mouse finds a name in brackets that it does not recognize as an
504existing type, it assumes that this is a class name, for example
505C<ArrayRef[DateTime]>.
506
507B<NOTE:> Unless you parameterize a type, then it is invalid to include
508the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
509name, I<not> as a parameterization of C<ArrayRef>.
510
511B<NOTE:> The C<Undef> type constraint for the most part works
512correctly now, but edge cases may still exist, please use it
513sparingly.
514
515B<NOTE:> The C<ClassName> type constraint does a complex package
516existence check. This means that your class B<must> be loaded for this
517type constraint to pass.
518
519B<NOTE:> The C<RoleName> constraint checks a string is a I<package
520name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
521constraint checks that an I<object does> the named role.
522
523=head2 Type Constraint Naming
524
525Type name declared via this module can only contain alphanumeric
526characters, colons (:), and periods (.).
527
528Since the types created by this module are global, it is suggested
529that you namespace your types just as you would namespace your
530modules. So instead of creating a I<Color> type for your
531B<My::Graphics> module, you would call the type
532I<My::Graphics::Types::Color> instead.
533
534=head2 Use with Other Constraint Modules
535
536This module can play nicely with other constraint modules with some
537slight tweaking. The C<where> clause in types is expected to be a
538C<CODE> reference which checks it's first argument and returns a
539boolean. Since most constraint modules work in a similar way, it
540should be simple to adapt them to work with Mouse.
541
542For instance, this is how you could use it with
543L<Declare::Constraints::Simple> to declare a completely new type.
544
545 type 'HashOfArrayOfObjects',
546 {
547 where => IsHashRef(
548 -keys => HasLength,
549 -values => IsArrayRef(IsObject)
550 )
551 };
552
553Here is an example of using L<Test::Deep> and it's non-test
554related C<eq_deeply> function.
555
556 type 'ArrayOfHashOfBarsAndRandomNumbers'
557 => where {
558 eq_deeply($_,
559 array_each(subhashof({
560 bar => isa('Bar'),
561 random_number => ignore()
562 })))
563 };
6feb83f1 564
565=head1 METHODS
566
24410e3a 567=head2 C<< list_all_builtin_type_constraints -> (Names) >>
6feb83f1 568
24410e3a 569Returns the names of builtin type constraints.
570
571=head2 C<< list_all_type_constraints -> (Names) >>
572
573Returns the names of all the type constraints.
6feb83f1 574
c91d12e0 575=head1 FUNCTIONS
576
577=over 4
578
1820fffe 579=item C<< subtype 'Name' => as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 580
1820fffe 581=item C<< subtype as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 582
1820fffe 583=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 584
1820fffe 585=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 586
1820fffe 587=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
588
589=back
590
591=over 4
592
593=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 594
595=back
596
5893ee36 597=head1 THANKS
598
1820fffe 599Much of this documentation was taken from C<Moose::Util::TypeConstraints>
600
601=head1 SEE ALSO
602
603L<Moose::Util::TypeConstraints>
5893ee36 604
6feb83f1 605=cut
606
607