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