Checking in changes prior to tagging of version 0.61.
[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
718b5d9b 4use Carp ();
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
718b5d9b 23our @CARP_NOT = qw(Mouse::Meta::Attribute);
24
cceb0e25 25my %TYPE;
4188b837 26
df448257 27# The root type
28$TYPE{Any} = Mouse::Meta::TypeConstraint->new(
29 name => 'Any',
30);
31
32my @builtins = (
33 # $name => $parent, $code,
34
35 # the base type
36 Item => 'Any', undef,
37
38 # the maybe[] type
39 Maybe => 'Item', undef,
40
41 # value types
42 Undef => 'Item', \&Undef,
43 Defined => 'Item', \&Defined,
44 Bool => 'Item', \&Bool,
45 Value => 'Defined', \&Value,
46 Str => 'Value', \&Str,
47 Num => 'Str', \&Num,
48 Int => 'Num', \&Int,
49
50 # ref types
51 Ref => 'Defined', \&Ref,
52 ScalarRef => 'Ref', \&ScalarRef,
53 ArrayRef => 'Ref', \&ArrayRef,
54 HashRef => 'Ref', \&HashRef,
55 CodeRef => 'Ref', \&CodeRef,
56 RegexpRef => 'Ref', \&RegexpRef,
57 GlobRef => 'Ref', \&GlobRef,
58
59 # object types
60 FileHandle => 'GlobRef', \&FileHandle,
61 Object => 'Ref', \&Object,
62
63 # special string types
64 ClassName => 'Str', \&ClassName,
65 RoleName => 'ClassName', \&RoleName,
66);
67
68
69while (my ($name, $parent, $code) = splice @builtins, 0, 3) {
70 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
71 name => $name,
72 parent => $TYPE{$parent},
73 optimized => $code,
74 );
75}
76
77# make it parametarizable
78
79$TYPE{Maybe} {constraint_generator} = \&_parameterize_Maybe_for;
80$TYPE{ArrayRef}{constraint_generator} = \&_parameterize_ArrayRef_for;
81$TYPE{HashRef} {constraint_generator} = \&_parameterize_HashRef_for;
82
83# sugars
84
a4b15169 85sub as ($) { (as => $_[0]) } ## no critic
86sub where (&) { (where => $_[0]) } ## no critic
87sub message (&) { (message => $_[0]) } ## no critic
88sub optimize_as (&) { (optimize_as => $_[0]) } ## no critic
61a02a3a 89
73766a27 90sub from { @_ }
a4b15169 91sub via (&) { $_[0] } ## no critic
61a02a3a 92
df448257 93# type utilities
993e62a7 94
df448257 95sub optimized_constraints { # DEPRECATED
96 Carp::cluck('optimized_constraints() has been deprecated');
97 return \%TYPE;
98}
d3982c7e 99
df448257 100undef @builtins; # free the allocated memory
101@builtins = keys %TYPE; # reuse it
102sub list_all_builtin_type_constraints { @builtins }
d4571def 103
df448257 104sub list_all_type_constraints { keys %TYPE }
993e62a7 105
f5ee065f 106sub _create_type{
107 my $mode = shift;
108
73766a27 109 my $name;
f5ee065f 110 my %args;
73766a27 111
f5ee065f 112 if(@_ == 1 && ref $_[0]){ # @_ : { name => $name, where => ... }
113 %args = %{$_[0]};
73766a27 114 }
f5ee065f 115 elsif(@_ == 2 && ref $_[1]){ # @_ : $name => { where => ... }
73766a27 116 $name = $_[0];
f5ee065f 117 %args = %{$_[1]};
73766a27 118 }
f5ee065f 119 elsif(@_ % 2){ # @_ : $name => ( where => ... )
120 ($name, %args) = @_;
73766a27 121 }
f5ee065f 122 else{ # @_ : (name => $name, where => ...)
123 %args = @_;
73766a27 124 }
125
f5ee065f 126 if(!defined $name){
4f24c598 127 $name = $args{name};
cd2b9201 128 }
d4571def 129
f5ee065f 130 $args{name} = $name;
b8434acc 131 my $parent;
132 if($mode eq 'subtype'){
133 $parent = delete $args{as};
134 if(!$parent){
135 $parent = delete $args{name};
4f24c598 136 $name = undef;
b8434acc 137 }
138 }
7dbebb1b 139
4f24c598 140 if(defined $name){
df448257 141 # set 'package_defined_in' only if it is not a core package
142 my $this = $args{package_defined_in};
143 if(!$this){
144 $this = caller(1);
145 if($this !~ /\A Mouse \b/xms){
146 $args{package_defined_in} = $this;
147 }
148 }
149
150 if($TYPE{$name}){
151 my $that = $TYPE{$name}->{package_defined_in} || __PACKAGE__;
718b5d9b 152 ($this eq $that) or Carp::croak(
df448257 153 "The type constraint '$name' has already been created in $that and cannot be created again in $this"
154 );
4f24c598 155 }
156 }
157 else{
158 $args{name} = '__ANON__';
f5ee065f 159 }
d4571def 160
b8434acc 161 $args{constraint} = delete $args{where} if exists $args{where};
24410e3a 162 $args{optimized} = delete $args{optimized_as} if exists $args{optimized_as};
0d9fea22 163
f5ee065f 164 my $constraint;
165 if($mode eq 'subtype'){
24410e3a 166 $constraint = find_or_create_isa_type_constraint($parent)->create_child_type(%args);
7a50b450 167 }
168 else{
f5ee065f 169 $constraint = Mouse::Meta::TypeConstraint->new(%args);
73766a27 170 }
7dbebb1b 171
4f24c598 172 if(defined $name){
173 return $TYPE{$name} = $constraint;
174 }
175 else{
176 return $constraint;
177 }
f5ee065f 178}
7dbebb1b 179
f5ee065f 180sub type {
181 return _create_type('type', @_);
182}
d4571def 183
f5ee065f 184sub subtype {
185 return _create_type('subtype', @_);
4188b837 186}
187
139d92d2 188sub coerce {
ffbbf459 189 my $type_name = shift;
61a02a3a 190
ffbbf459 191 my $type = find_type_constraint($type_name)
718b5d9b 192 or Carp::croak("Cannot find type '$type_name', perhaps you forgot to load it.");
61a02a3a 193
ffbbf459 194 $type->_add_type_coercions(@_);
cd2b9201 195 return;
4188b837 196}
197
139d92d2 198sub class_type {
337e3b0c 199 my($name, $options) = @_;
200 my $class = $options->{class} || $name;
df448257 201
202 # ClassType
337e3b0c 203 return _create_type 'subtype', $name => (
ddbad0b1 204 as => 'Object',
e3540312 205 optimized_as => Mouse::Util::generate_isa_predicate_for($class),
337e3b0c 206 );
ecc6e3b1 207}
208
139d92d2 209sub role_type {
337e3b0c 210 my($name, $options) = @_;
211 my $role = $options->{role} || $name;
df448257 212
213 # RoleType
337e3b0c 214 return _create_type 'subtype', $name => (
1d5ecd5f 215 as => 'Object',
b7d1f970 216 optimized_as => sub { Scalar::Util::blessed($_[0]) && does_role($_[0], $role) },
61a02a3a 217 );
47f36c05 218}
219
ebe91068 220sub duck_type {
221 my($name, @methods);
222
223 if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){
224 $name = shift;
225 }
226
227 @methods = (@_ == 1 && ref($_[0]) eq 'ARRAY') ? @{$_[0]} : @_;
228
df448257 229 # DuckType
188ff28f 230 return _create_type 'subtype', $name => (
231 as => 'Object',
ebe91068 232 optimized_as => Mouse::Util::generate_can_predicate_for(\@methods),
ebe91068 233 );
234}
235
d44f0d03 236sub enum {
f5ee065f 237 my($name, %valid);
238
f152b099 239 if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){
240 $name = shift;
f5ee065f 241 }
f152b099 242
243 %valid = map{ $_ => undef } (@_ == 1 && ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_);
244
df448257 245 # EnumType
188ff28f 246 return _create_type 'subtype', $name => (
247 as => 'Str',
f5ee065f 248 optimized_as => sub{ defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]} },
f5ee065f 249 );
250}
251
252sub _find_or_create_regular_type{
718b5d9b 253 my($spec, $create) = @_;
f5ee065f 254
255 return $TYPE{$spec} if exists $TYPE{$spec};
01904723 256
718b5d9b 257 my $meta = Mouse::Util::get_metaclass_by_name($spec);
258
259 if(!defined $meta){
260 return $create ? class_type($spec) : undef;
261 }
01904723 262
f48920c1 263 if(Mouse::Util::is_a_metarole($meta)){
1d5ecd5f 264 return role_type($spec);
f5ee065f 265 }
266 else{
1d5ecd5f 267 return class_type($spec);
f5ee065f 268 }
d44f0d03 269}
270
f5ee065f 271sub _find_or_create_parameterized_type{
272 my($base, $param) = @_;
273
274 my $name = sprintf '%s[%s]', $base->name, $param->name;
275
b4d791ba 276 $TYPE{$name} ||= $base->parameterize($param, $name);
f5ee065f 277}
b4d791ba 278
f5ee065f 279sub _find_or_create_union_type{
718b5d9b 280 return if grep{ not defined } @_;
a2f1294a 281 my @types = sort map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
f5ee065f 282
30b28db3 283 my $name = join '|', @types;
f5ee065f 284
df448257 285 # UnionType
b4d791ba 286 $TYPE{$name} ||= Mouse::Meta::TypeConstraint->new(
287 name => $name,
288 type_constraints => \@types,
b4d791ba 289 );
f5ee065f 290}
291
292# The type parser
f5ee065f 293
718b5d9b 294# param : '[' type ']' | NOTHING
295sub _parse_param {
296 my($c) = @_;
f5ee065f 297
718b5d9b 298 if($c->{spec} =~ s/^\[//){
299 my $type = _parse_type($c, 1);
f5ee065f 300
718b5d9b 301 if($c->{spec} =~ s/^\]//){
302 return $type;
321e5271 303 }
718b5d9b 304 Carp::croak("Syntax error in type: missing right square bracket in '$c->{orig}'");
305 }
57f0e313 306
718b5d9b 307 return undef;
308}
f5ee065f 309
718b5d9b 310# name : [\w.:]+
311sub _parse_name {
312 my($c, $create) = @_;
f5ee065f 313
718b5d9b 314 if($c->{spec} =~ s/\A ([\w.:]+) //xms){
315 return _find_or_create_regular_type($1, $create);
316 }
317 Carp::croak("Syntax error in type: expect type name near '$c->{spec}' in '$c->{orig}'");
318}
f5ee065f 319
718b5d9b 320# single_type : name param
321sub _parse_single_type {
322 my($c, $create) = @_;
f5ee065f 323
718b5d9b 324 my $type = _parse_name($c, $create);
325 my $param = _parse_param($c);
29376895 326
718b5d9b 327 if(defined $type){
328 if(defined $param){
329 return _find_or_create_parameterized_type($type, $param);
29376895 330 }
718b5d9b 331 else {
332 return $type;
29376895 333 }
f5ee065f 334 }
718b5d9b 335 elsif(defined $param){
336 Carp::croak("Undefined type with parameter [$param] in '$c->{orig}'");
993e62a7 337 }
338 else{
718b5d9b 339 return undef;
993e62a7 340 }
321e5271 341}
342
718b5d9b 343# type : single_type ('|' single_type)*
344sub _parse_type {
345 my($c, $create) = @_;
346
347 my $type = _parse_single_type($c, $create);
348 if($c->{spec}){ # can be an union type
349 my @types;
350 while($c->{spec} =~ s/^\|//){
351 push @types, _parse_single_type($c, $create);
352 }
353 if(@types){
354 return _find_or_create_union_type($type, @types);
355 }
356 }
357 return $type;
358}
359
f5ee065f 360
361sub find_type_constraint {
362 my($spec) = @_;
f48920c1 363 return $spec if Mouse::Util::is_a_type_constraint($spec);
df448257 364 return undef if !defined $spec;
f5ee065f 365
366 $spec =~ s/\s+//g;
367 return $TYPE{$spec};
2efc0af1 368}
369
f5ee065f 370sub find_or_parse_type_constraint {
371 my($spec) = @_;
f48920c1 372 return $spec if Mouse::Util::is_a_type_constraint($spec);
df448257 373 return undef if !defined $spec;
9c85e9dc 374
f5ee065f 375 $spec =~ s/\s+//g;
376 return $TYPE{$spec} || do{
718b5d9b 377 my $context = {
378 spec => $spec,
379 orig => $spec,
380 };
381 my $type = _parse_type($context);
382
383 if($context->{spec}){
384 Carp::croak("Syntax error: extra elements '$context->{spec}' in '$context->{orig}'");
385 }
f5ee065f 386 $type;
387 };
388}
321e5271 389
f5ee065f 390sub find_or_create_does_type_constraint{
ddbad0b1 391 # XXX: Moose does not register a new role_type, but Mouse does.
bddbe49f 392 return find_or_parse_type_constraint(@_) || role_type(@_);
f5ee065f 393}
394
395sub find_or_create_isa_type_constraint {
ddbad0b1 396 # XXX: Moose does not register a new class_type, but Mouse does.
f5ee065f 397 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 398}
399
d60c78b9 4001;
6feb83f1 401__END__
402
403=head1 NAME
404
5893ee36 405Mouse::Util::TypeConstraints - Type constraint system for Mouse
406
a25ca8d6 407=head1 VERSION
408
913b5964 409This document describes Mouse version 0.61
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
dba2e142 477 Any
5893ee36 478 Item
479 Bool
480 Maybe[`a]
481 Undef
482 Defined
483 Value
5893ee36 484 Str
dba2e142 485 Num
486 Int
487 ClassName
488 RoleName
5893ee36 489 Ref
490 ScalarRef
491 ArrayRef[`a]
492 HashRef[`a]
493 CodeRef
494 RegexpRef
495 GlobRef
dba2e142 496 FileHandle
5893ee36 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
5893ee36 510B<NOTE:> The C<Undef> type constraint for the most part works
511correctly now, but edge cases may still exist, please use it
512sparingly.
513
514B<NOTE:> The C<ClassName> type constraint does a complex package
515existence check. This means that your class B<must> be loaded for this
516type constraint to pass.
517
518B<NOTE:> The C<RoleName> constraint checks a string is a I<package
519name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
520constraint checks that an I<object does> the named role.
521
522=head2 Type Constraint Naming
523
524Type name declared via this module can only contain alphanumeric
525characters, colons (:), and periods (.).
526
527Since the types created by this module are global, it is suggested
528that you namespace your types just as you would namespace your
529modules. So instead of creating a I<Color> type for your
530B<My::Graphics> module, you would call the type
531I<My::Graphics::Types::Color> instead.
532
533=head2 Use with Other Constraint Modules
534
535This module can play nicely with other constraint modules with some
536slight tweaking. The C<where> clause in types is expected to be a
537C<CODE> reference which checks it's first argument and returns a
538boolean. Since most constraint modules work in a similar way, it
539should be simple to adapt them to work with Mouse.
540
541For instance, this is how you could use it with
542L<Declare::Constraints::Simple> to declare a completely new type.
543
544 type 'HashOfArrayOfObjects',
545 {
546 where => IsHashRef(
547 -keys => HasLength,
548 -values => IsArrayRef(IsObject)
549 )
550 };
551
552Here is an example of using L<Test::Deep> and it's non-test
553related C<eq_deeply> function.
554
555 type 'ArrayOfHashOfBarsAndRandomNumbers'
556 => where {
557 eq_deeply($_,
558 array_each(subhashof({
559 bar => isa('Bar'),
560 random_number => ignore()
561 })))
562 };
6feb83f1 563
564=head1 METHODS
565
24410e3a 566=head2 C<< list_all_builtin_type_constraints -> (Names) >>
6feb83f1 567
24410e3a 568Returns the names of builtin type constraints.
569
570=head2 C<< list_all_type_constraints -> (Names) >>
571
572Returns the names of all the type constraints.
6feb83f1 573
c91d12e0 574=head1 FUNCTIONS
575
576=over 4
577
c9cc6884 578=item C<< type $name => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 579
c9cc6884 580=item C<< subtype $name => as $parent => where { } ... -> Mouse::Meta::TypeConstraint >>
581
582=item C<< subtype as $parent => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 583
1820fffe 584=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 585
1820fffe 586=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 587
c9cc6884 588=item C<< duck_type($name, @methods | \@methods) -> Mouse::Meta::TypeConstraint >>
589
590=item C<< duck_type(\@methods) -> Mouse::Meta::TypeConstraint >>
591
592=item C<< enum($name, @values | \@values) -> Mouse::Meta::TypeConstraint >>
593
1820fffe 594=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
595
87f1f3d2 596=item C<< coerce $type => from $another_type, via { }, ... >>
597
1820fffe 598=back
599
600=over 4
601
602=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 603
604=back
605
5893ee36 606=head1 THANKS
607
1820fffe 608Much of this documentation was taken from C<Moose::Util::TypeConstraints>
609
610=head1 SEE ALSO
611
612L<Moose::Util::TypeConstraints>
5893ee36 613
6feb83f1 614=cut
615
616