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