Resolve some TODO tests about type constraints
[gitmo/Mouse.git] / lib / Mouse / Util / TypeConstraints.pm
CommitLineData
3b46bd49 1package Mouse::Util::TypeConstraints;
89681b0b 2use Mouse::Util; # 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',
89681b0b 226 optimized_as => sub {
227 return Scalar::Util::blessed($_[0])
228 && Mouse::Util::does_role($_[0], $role);
229 },
61a02a3a 230 );
47f36c05 231}
232
ebe91068 233sub duck_type {
234 my($name, @methods);
235
5a592ad7 236 if(ref($_[0]) ne 'ARRAY'){
ebe91068 237 $name = shift;
238 }
239
240 @methods = (@_ == 1 && ref($_[0]) eq 'ARRAY') ? @{$_[0]} : @_;
241
df448257 242 # DuckType
188ff28f 243 return _create_type 'subtype', $name => (
244 as => 'Object',
ebe91068 245 optimized_as => Mouse::Util::generate_can_predicate_for(\@methods),
5a592ad7 246 message => sub {
247 my($object) = @_;
248 my @missing = grep { !$object->can($_) } @methods;
249 return ref($object)
250 . ' is missing methods '
251 . Mouse::Util::quoted_english_list(@missing);
252 },
ebe91068 253 );
254}
255
d44f0d03 256sub enum {
f5ee065f 257 my($name, %valid);
258
f152b099 259 if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){
260 $name = shift;
f5ee065f 261 }
f152b099 262
89681b0b 263 %valid = map{ $_ => undef }
264 (@_ == 1 && ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_);
f152b099 265
df448257 266 # EnumType
188ff28f 267 return _create_type 'subtype', $name => (
268 as => 'Str',
89681b0b 269 optimized_as => sub{
270 return defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]};
271 },
f5ee065f 272 );
273}
274
275sub _find_or_create_regular_type{
718b5d9b 276 my($spec, $create) = @_;
f5ee065f 277
278 return $TYPE{$spec} if exists $TYPE{$spec};
01904723 279
718b5d9b 280 my $meta = Mouse::Util::get_metaclass_by_name($spec);
281
282 if(!defined $meta){
283 return $create ? class_type($spec) : undef;
284 }
01904723 285
f48920c1 286 if(Mouse::Util::is_a_metarole($meta)){
1d5ecd5f 287 return role_type($spec);
f5ee065f 288 }
289 else{
1d5ecd5f 290 return class_type($spec);
f5ee065f 291 }
d44f0d03 292}
293
f5ee065f 294sub _find_or_create_parameterized_type{
295 my($base, $param) = @_;
296
297 my $name = sprintf '%s[%s]', $base->name, $param->name;
298
b4d791ba 299 $TYPE{$name} ||= $base->parameterize($param, $name);
f5ee065f 300}
b4d791ba 301
f5ee065f 302sub _find_or_create_union_type{
718b5d9b 303 return if grep{ not defined } @_;
a2f1294a 304 my @types = sort map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
f5ee065f 305
30b28db3 306 my $name = join '|', @types;
f5ee065f 307
df448257 308 # UnionType
b4d791ba 309 $TYPE{$name} ||= Mouse::Meta::TypeConstraint->new(
310 name => $name,
311 type_constraints => \@types,
b4d791ba 312 );
f5ee065f 313}
314
315# The type parser
f5ee065f 316
718b5d9b 317# param : '[' type ']' | NOTHING
318sub _parse_param {
319 my($c) = @_;
f5ee065f 320
718b5d9b 321 if($c->{spec} =~ s/^\[//){
322 my $type = _parse_type($c, 1);
f5ee065f 323
718b5d9b 324 if($c->{spec} =~ s/^\]//){
325 return $type;
321e5271 326 }
718b5d9b 327 Carp::croak("Syntax error in type: missing right square bracket in '$c->{orig}'");
328 }
57f0e313 329
718b5d9b 330 return undef;
331}
f5ee065f 332
718b5d9b 333# name : [\w.:]+
334sub _parse_name {
335 my($c, $create) = @_;
f5ee065f 336
718b5d9b 337 if($c->{spec} =~ s/\A ([\w.:]+) //xms){
338 return _find_or_create_regular_type($1, $create);
339 }
340 Carp::croak("Syntax error in type: expect type name near '$c->{spec}' in '$c->{orig}'");
341}
f5ee065f 342
718b5d9b 343# single_type : name param
344sub _parse_single_type {
345 my($c, $create) = @_;
f5ee065f 346
718b5d9b 347 my $type = _parse_name($c, $create);
348 my $param = _parse_param($c);
29376895 349
718b5d9b 350 if(defined $type){
351 if(defined $param){
352 return _find_or_create_parameterized_type($type, $param);
29376895 353 }
718b5d9b 354 else {
355 return $type;
29376895 356 }
f5ee065f 357 }
718b5d9b 358 elsif(defined $param){
359 Carp::croak("Undefined type with parameter [$param] in '$c->{orig}'");
993e62a7 360 }
361 else{
718b5d9b 362 return undef;
993e62a7 363 }
321e5271 364}
365
718b5d9b 366# type : single_type ('|' single_type)*
367sub _parse_type {
368 my($c, $create) = @_;
369
370 my $type = _parse_single_type($c, $create);
371 if($c->{spec}){ # can be an union type
372 my @types;
373 while($c->{spec} =~ s/^\|//){
374 push @types, _parse_single_type($c, $create);
375 }
376 if(@types){
377 return _find_or_create_union_type($type, @types);
378 }
379 }
380 return $type;
381}
382
f5ee065f 383
384sub find_type_constraint {
385 my($spec) = @_;
7712ea96 386 return $spec if Mouse::Util::is_a_type_constraint($spec) or not defined $spec;
f5ee065f 387
388 $spec =~ s/\s+//g;
389 return $TYPE{$spec};
2efc0af1 390}
391
35ce550a 392sub register_type_constraint {
393 my($constraint) = @_;
394 Carp::croak("No type supplied / type is not a valid type constraint")
395 unless Mouse::Util::is_a_type_constraint($constraint);
396 my $name = $constraint->name;
397 Carp::croak("can't register an unnamed type constraint")
398 unless defined $name;
399 return $TYPE{$name} = $constraint;
400}
401
f5ee065f 402sub find_or_parse_type_constraint {
403 my($spec) = @_;
7712ea96 404 return $spec if Mouse::Util::is_a_type_constraint($spec) or not defined $spec;
9c85e9dc 405
f5ee065f 406 $spec =~ s/\s+//g;
407 return $TYPE{$spec} || do{
718b5d9b 408 my $context = {
409 spec => $spec,
410 orig => $spec,
411 };
412 my $type = _parse_type($context);
413
414 if($context->{spec}){
415 Carp::croak("Syntax error: extra elements '$context->{spec}' in '$context->{orig}'");
416 }
f5ee065f 417 $type;
418 };
419}
321e5271 420
f5ee065f 421sub find_or_create_does_type_constraint{
ddbad0b1 422 # XXX: Moose does not register a new role_type, but Mouse does.
bddbe49f 423 return find_or_parse_type_constraint(@_) || role_type(@_);
f5ee065f 424}
425
426sub find_or_create_isa_type_constraint {
ddbad0b1 427 # XXX: Moose does not register a new class_type, but Mouse does.
f5ee065f 428 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 429}
430
d60c78b9 4311;
6feb83f1 432__END__
433
434=head1 NAME
435
5893ee36 436Mouse::Util::TypeConstraints - Type constraint system for Mouse
437
a25ca8d6 438=head1 VERSION
439
43c1bb1a 440This document describes Mouse version 0.71
a25ca8d6 441
5893ee36 442=head2 SYNOPSIS
443
444 use Mouse::Util::TypeConstraints;
445
446 subtype 'Natural'
447 => as 'Int'
448 => where { $_ > 0 };
449
450 subtype 'NaturalLessThanTen'
451 => as 'Natural'
452 => where { $_ < 10 }
453 => message { "This number ($_) is not less than ten!" };
454
455 coerce 'Num'
456 => from 'Str'
457 => via { 0+$_ };
458
459 enum 'RGBColors' => qw(red green blue);
460
461 no Mouse::Util::TypeConstraints;
462
463=head1 DESCRIPTION
464
465This module provides Mouse with the ability to create custom type
466constraints to be used in attribute definition.
467
468=head2 Important Caveat
469
470This is B<NOT> a type system for Perl 5. These are type constraints,
471and they are not used by Mouse unless you tell it to. No type
472inference is performed, expressions are not typed, etc. etc. etc.
473
474A type constraint is at heart a small "check if a value is valid"
475function. A constraint can be associated with an attribute. This
476simplifies parameter validation, and makes your code clearer to read,
477because you can refer to constraints by name.
478
479=head2 Slightly Less Important Caveat
480
481It is B<always> a good idea to quote your type names.
482
483This prevents Perl from trying to execute the call as an indirect
484object call. This can be an issue when you have a subtype with the
485same name as a valid class.
486
487For instance:
488
489 subtype DateTime => as Object => where { $_->isa('DateTime') };
490
491will I<just work>, while this:
492
493 use DateTime;
494 subtype DateTime => as Object => where { $_->isa('DateTime') };
495
496will fail silently and cause many headaches. The simple way to solve
497this, as well as future proof your subtypes from classes which have
498yet to have been created, is to quote the type name:
499
500 use DateTime;
501 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
502
503=head2 Default Type Constraints
504
505This module also provides a simple hierarchy for Perl 5 types, here is
506that hierarchy represented visually.
507
dba2e142 508 Any
5893ee36 509 Item
510 Bool
511 Maybe[`a]
512 Undef
513 Defined
514 Value
5893ee36 515 Str
dba2e142 516 Num
517 Int
518 ClassName
519 RoleName
5893ee36 520 Ref
521 ScalarRef
522 ArrayRef[`a]
523 HashRef[`a]
524 CodeRef
525 RegexpRef
526 GlobRef
dba2e142 527 FileHandle
5893ee36 528 Object
5893ee36 529
530B<NOTE:> Any type followed by a type parameter C<[`a]> can be
531parameterized, this means you can say:
532
533 ArrayRef[Int] # an array of integers
534 HashRef[CodeRef] # a hash of str to CODE ref mappings
535 Maybe[Str] # value may be a string, may be undefined
536
537If Mouse finds a name in brackets that it does not recognize as an
538existing type, it assumes that this is a class name, for example
539C<ArrayRef[DateTime]>.
540
5893ee36 541B<NOTE:> The C<Undef> type constraint for the most part works
542correctly now, but edge cases may still exist, please use it
543sparingly.
544
545B<NOTE:> The C<ClassName> type constraint does a complex package
546existence check. This means that your class B<must> be loaded for this
547type constraint to pass.
548
549B<NOTE:> The C<RoleName> constraint checks a string is a I<package
550name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
551constraint checks that an I<object does> the named role.
552
553=head2 Type Constraint Naming
554
555Type name declared via this module can only contain alphanumeric
556characters, colons (:), and periods (.).
557
558Since the types created by this module are global, it is suggested
559that you namespace your types just as you would namespace your
560modules. So instead of creating a I<Color> type for your
561B<My::Graphics> module, you would call the type
562I<My::Graphics::Types::Color> instead.
563
564=head2 Use with Other Constraint Modules
565
566This module can play nicely with other constraint modules with some
567slight tweaking. The C<where> clause in types is expected to be a
568C<CODE> reference which checks it's first argument and returns a
569boolean. Since most constraint modules work in a similar way, it
570should be simple to adapt them to work with Mouse.
571
572For instance, this is how you could use it with
573L<Declare::Constraints::Simple> to declare a completely new type.
574
575 type 'HashOfArrayOfObjects',
576 {
577 where => IsHashRef(
578 -keys => HasLength,
579 -values => IsArrayRef(IsObject)
580 )
581 };
582
583Here is an example of using L<Test::Deep> and it's non-test
584related C<eq_deeply> function.
585
586 type 'ArrayOfHashOfBarsAndRandomNumbers'
587 => where {
588 eq_deeply($_,
589 array_each(subhashof({
590 bar => isa('Bar'),
591 random_number => ignore()
592 })))
593 };
6feb83f1 594
595=head1 METHODS
596
24410e3a 597=head2 C<< list_all_builtin_type_constraints -> (Names) >>
6feb83f1 598
24410e3a 599Returns the names of builtin type constraints.
600
601=head2 C<< list_all_type_constraints -> (Names) >>
602
603Returns the names of all the type constraints.
6feb83f1 604
c91d12e0 605=head1 FUNCTIONS
606
607=over 4
608
c9cc6884 609=item C<< type $name => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 610
c9cc6884 611=item C<< subtype $name => as $parent => where { } ... -> Mouse::Meta::TypeConstraint >>
612
613=item C<< subtype as $parent => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 614
1820fffe 615=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 616
1820fffe 617=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 618
c9cc6884 619=item C<< duck_type($name, @methods | \@methods) -> Mouse::Meta::TypeConstraint >>
620
621=item C<< duck_type(\@methods) -> Mouse::Meta::TypeConstraint >>
622
623=item C<< enum($name, @values | \@values) -> Mouse::Meta::TypeConstraint >>
624
1820fffe 625=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
626
87f1f3d2 627=item C<< coerce $type => from $another_type, via { }, ... >>
628
1820fffe 629=back
630
631=over 4
632
633=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 634
635=back
636
5893ee36 637=head1 THANKS
638
1820fffe 639Much of this documentation was taken from C<Moose::Util::TypeConstraints>
640
641=head1 SEE ALSO
642
643L<Moose::Util::TypeConstraints>
5893ee36 644
6feb83f1 645=cut
646
647