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