Add a test for deprecated feature
[gitmo/Mouse.git] / lib / Mouse / Util / TypeConstraints.pm
CommitLineData
3b46bd49 1package Mouse::Util::TypeConstraints;
d60c78b9 2use strict;
3use warnings;
f3bb863f 4
5use Exporter;
9baf5d6b 6
f5ee065f 7use Carp qw(confess);
6c169c50 8use Scalar::Util qw/blessed looks_like_number openhandle/;
6d28c5cf 9
2efc0af1 10use Mouse::Util qw(does_role not_supported);
a497c7d3 11use Mouse::Meta::Module; # class_of
684db121 12use Mouse::Meta::TypeConstraint;
d60c78b9 13
f5ee065f 14use constant _DEBUG => !!$ENV{TC_DEBUG};
15
f3bb863f 16our @ISA = qw(Exporter);
139d92d2 17our @EXPORT = qw(
d44f0d03 18 as where message from via type subtype coerce class_type role_type enum
ccf44227 19 find_type_constraint
139d92d2 20);
21
cceb0e25 22my %TYPE;
8a7f2a8a 23my %COERCE;
24my %COERCE_KEYS;
4188b837 25
139d92d2 26sub as ($) {
cd2b9201 27 return(as => $_[0]);
61a02a3a 28}
139d92d2 29sub where (&) {
cd2b9201 30 return(where => $_[0])
61a02a3a 31}
0f1dae9a 32sub message (&) {
cd2b9201 33 return(message => $_[0])
61a02a3a 34}
35
73766a27 36sub from { @_ }
cd2b9201 37sub via (&) { $_[0] }
61a02a3a 38
321e5271 39BEGIN {
993e62a7 40 my %builtins = (
f5ee065f 41 Any => undef, # null check
42 Item => undef, # null check
43 Maybe => undef, # null check
73766a27 44
45 Bool => sub { $_[0] ? $_[0] eq '1' : 1 },
c91d12e0 46 Undef => sub { !defined($_[0]) },
47 Defined => sub { defined($_[0]) },
48 Value => sub { defined($_[0]) && !ref($_[0]) },
49 Num => sub { !ref($_[0]) && looks_like_number($_[0]) },
50 Int => sub { defined($_[0]) && !ref($_[0]) && $_[0] =~ /^-?[0-9]+$/ },
51 Str => sub { defined($_[0]) && !ref($_[0]) },
c91d12e0 52 Ref => sub { ref($_[0]) },
53
54 ScalarRef => sub { ref($_[0]) eq 'SCALAR' },
55 ArrayRef => sub { ref($_[0]) eq 'ARRAY' },
56 HashRef => sub { ref($_[0]) eq 'HASH' },
57 CodeRef => sub { ref($_[0]) eq 'CODE' },
58 RegexpRef => sub { ref($_[0]) eq 'Regexp' },
59 GlobRef => sub { ref($_[0]) eq 'GLOB' },
381f326a 60
61 FileHandle => sub {
c91d12e0 62 ref($_[0]) eq 'GLOB' && openhandle($_[0])
381f326a 63 or
c91d12e0 64 blessed($_[0]) && $_[0]->isa("IO::Handle")
abe4e887 65 },
381f326a 66
c91d12e0 67 Object => sub { blessed($_[0]) && blessed($_[0]) ne 'Regexp' },
73766a27 68
69 ClassName => sub { Mouse::Util::is_class_loaded($_[0]) },
d4571def 70 RoleName => sub { (Mouse::Util::find_meta($_[0]) || return 0)->isa('Mouse::Meta::Role') },
8a7f2a8a 71 );
993e62a7 72
73 while (my ($name, $code) = each %builtins) {
73766a27 74 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
75 name => $name,
76 _compiled_type_constraint => $code,
f5ee065f 77 package_defined_in => __PACKAGE__,
73766a27 78 );
78b13827 79 }
d3982c7e 80
f5ee065f 81 sub optimized_constraints {
82 Carp::cluck('optimized_constraints() has been deprecated');
83 return \%TYPE;
84 }
d4571def 85
993e62a7 86 my @builtins = keys %TYPE;
87 sub list_all_builtin_type_constraints { @builtins }
88
89 sub list_all_type_constraints { keys %TYPE }
381f326a 90}
d3982c7e 91
f5ee065f 92sub _create_type{
93 my $mode = shift;
94
73766a27 95 my $name;
f5ee065f 96 my %args;
73766a27 97
f5ee065f 98 if(@_ == 1 && ref $_[0]){ # @_ : { name => $name, where => ... }
99 %args = %{$_[0]};
73766a27 100 }
f5ee065f 101 elsif(@_ == 2 && ref $_[1]){ # @_ : $name => { where => ... }
73766a27 102 $name = $_[0];
f5ee065f 103 %args = %{$_[1]};
73766a27 104 }
f5ee065f 105 elsif(@_ % 2){ # @_ : $name => ( where => ... )
106 ($name, %args) = @_;
73766a27 107 }
f5ee065f 108 else{ # @_ : (name => $name, where => ...)
109 %args = @_;
73766a27 110 }
111
f5ee065f 112 if(!defined $name){
113 if(!defined($name = $args{name})){
114 $name = '__ANON__';
115 }
cd2b9201 116 }
d4571def 117
f5ee065f 118 $args{name} = $name;
7dbebb1b 119
f5ee065f 120 my $package_defined_in = $args{package_defined_in} ||= caller(1);
d4571def 121
f5ee065f 122 my $existing = $TYPE{$name};
123 if($existing && $existing->{package_defined_in} ne $package_defined_in){
124 confess("The type constraint '$name' has already been created in "
125 . "$existing->{package_defined_in} and cannot be created again in $package_defined_in");
126 }
d4571def 127
f5ee065f 128 $args{constraint} = delete($args{where})
129 if exists $args{where};
130 $args{_compiled_type_constraint} = delete $args{optimized_as}
131 if exists $args{optimized_as};
0d9fea22 132
f5ee065f 133 my $constraint;
134 if($mode eq 'subtype'){
135 my $parent = exists($args{as}) ? delete($args{as}) : delete($args{name});
7a50b450 136
e98220ab 137 $parent = find_or_create_isa_type_constraint($parent);
f5ee065f 138 $constraint = $parent->create_child_type(%args);
7a50b450 139 }
140 else{
f5ee065f 141 $constraint = Mouse::Meta::TypeConstraint->new(%args);
73766a27 142 }
7dbebb1b 143
f5ee065f 144 return $TYPE{$name} = $constraint;
145}
7dbebb1b 146
f5ee065f 147sub type {
148 return _create_type('type', @_);
149}
d4571def 150
f5ee065f 151sub subtype {
152 return _create_type('subtype', @_);
4188b837 153}
154
139d92d2 155sub coerce {
cd2b9201 156 my $name = shift;
61a02a3a 157
f5ee065f 158 $name =~ s/\s+//g;
159 confess "Cannot find type '$name', perhaps you forgot to load it."
cceb0e25 160 unless $TYPE{$name};
61a02a3a 161
8a7f2a8a 162 unless ($COERCE{$name}) {
163 $COERCE{$name} = {};
164 $COERCE_KEYS{$name} = [];
165 }
cd2b9201 166
f5ee065f 167 my $package_defined_in = caller;
61a02a3a 168
e98220ab 169 while (my($from, $action) = splice @_, 0, 2) {
f5ee065f 170 $from =~ s/\s+//g;
171
172 confess "A coercion action already exists for '$from'"
173 if $COERCE{$name}->{$from};
174
175 my $type = find_or_parse_type_constraint($from, $package_defined_in);
176 if (!$type) {
177 confess "Could not find the type constraint ($from) to coerce from"
310ad28b 178 }
61a02a3a 179
f5ee065f 180 warn "# REGISTER COERCE $name, from $type\n" if _DEBUG;
181
cd2b9201 182 push @{ $COERCE_KEYS{$name} }, $type;
e98220ab 183 $COERCE{$name}->{$from} = $action;
61a02a3a 184 }
cd2b9201 185 return;
4188b837 186}
187
139d92d2 188sub class_type {
ecc6e3b1 189 my($name, $conf) = @_;
d9f8c878 190 if ($conf && $conf->{class}) {
191 # No, you're using this wrong
192 warn "class_type() should be class_type(ClassName). Perhaps you're looking for subtype $name => as '$conf->{class}'?";
f5ee065f 193 _create_type 'type', $name => (
194 as => $conf->{class},
195
196 type => 'Class',
197 );
a497c7d3 198 }
199 else {
f5ee065f 200 _create_type 'type', $name => (
201 optimized_as => sub { blessed($_[0]) && $_[0]->isa($name) },
202
203 type => 'Class',
d9f8c878 204 );
205 }
ecc6e3b1 206}
207
139d92d2 208sub role_type {
47f36c05 209 my($name, $conf) = @_;
f5ee065f 210 my $role = ($conf && $conf->{role}) ? $conf->{role} : $name;
211 _create_type 'type', $name => (
212 optimized_as => sub { blessed($_[0]) && does_role($_[0], $role) },
213
214 type => 'Role',
61a02a3a 215 );
47f36c05 216}
217
684db121 218# this is an original method for Mouse
4188b837 219sub typecast_constraints {
684db121 220 my($class, $pkg, $types, $value) = @_;
2efc0af1 221 Carp::croak("wrong arguments count") unless @_ == 4;
eec1bb49 222
b3b74cc6 223 local $_;
e98220ab 224 for my $type ($types->{type_constraints} ? @{$types->{type_constraints}} : $types ) {
8a7f2a8a 225 for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
f5ee065f 226
227 if(_DEBUG){
228 warn sprintf "# COERCE: from %s to %s for %s (%s)\n",
e98220ab 229 $coerce_type, $type, defined($value) ? "'$value'" : 'undef',
f5ee065f 230 $coerce_type->check($value) ? "try" : "skip";
231 }
232
233 next if !$coerce_type->check($value);
234
235 # try to coerce
b3b74cc6 236 $_ = $value;
e98220ab 237 my $coerced = $COERCE{$type}->{$coerce_type}->($value); # coerce
f5ee065f 238
239 if(_DEBUG){
240 warn sprintf "# COERCE: got %s, which is%s %s\n",
e98220ab 241 defined($coerced) ? $coerced : 'undef', $types->check($coerced) ? '' : ' not', $types;
f5ee065f 242 }
243
e98220ab 244 # check with $types, not $constraint
245 return $coerced if $types->check($coerced);
4188b837 246 }
247 }
e98220ab 248 return $value; # returns original $value
4188b837 249}
250
d44f0d03 251sub enum {
f5ee065f 252 my($name, %valid);
253
01904723 254 # enum ['small', 'medium', 'large']
255 if (ref($_[0]) eq 'ARRAY') {
f5ee065f 256 %valid = map{ $_ => undef } @{ $_[0] };
257 $name = sprintf '(%s)', join '|', sort @{$_[0]};
258 }
259 # enum size => 'small', 'medium', 'large'
260 else{
261 $name = shift;
262 %valid = map{ $_ => undef } @_;
263 }
264 return _create_type 'type', $name => (
265 optimized_as => sub{ defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]} },
266
267 type => 'Enum',
268 );
269}
270
271sub _find_or_create_regular_type{
272 my($spec) = @_;
273
274 return $TYPE{$spec} if exists $TYPE{$spec};
01904723 275
f5ee065f 276 my $meta = Mouse::Meta::Module::class_of($spec);
277
278 if(!$meta){
279 return;
01904723 280 }
281
f5ee065f 282 my $check;
283 my $type;
284 if($meta && $meta->isa('Mouse::Meta::Role')){
285 $check = sub{
286 return blessed($_[0]) && $_[0]->does($spec);
287 };
288 $type = 'Role';
289 }
290 else{
291 $check = sub{
292 return blessed($_[0]) && $_[0]->isa($spec);
293 };
294 $type = 'Class';
295 }
296
297 warn "#CREATE a $type type for $spec\n" if _DEBUG;
d44f0d03 298
f5ee065f 299 return $TYPE{$spec} = Mouse::Meta::TypeConstraint->new(
300 name => $spec,
301 _compiled_type_constraint => $check,
302
303 type => $type,
d44f0d03 304 );
305}
306
f5ee065f 307$TYPE{ArrayRef}{constraint_generator} = sub {
308 my($type_parameter) = @_;
309 my $check = $type_parameter->{_compiled_type_constraint};
321e5271 310
f5ee065f 311 return sub{
312 foreach my $value (@{$_}) {
313 return undef unless $check->($value);
314 }
315 return 1;
316 }
317};
318$TYPE{HashRef}{constraint_generator} = sub {
319 my($type_parameter) = @_;
320 my $check = $type_parameter->{_compiled_type_constraint};
321
322 return sub{
323 foreach my $value(values %{$_}){
324 return undef unless $check->($value);
325 }
326 return 1;
327 };
328};
2efc0af1 329
f5ee065f 330# 'Maybe' type accepts 'Any', so it requires parameters
331$TYPE{Maybe}{constraint_generator} = sub {
332 my($type_parameter) = @_;
333 my $check = $type_parameter->{_compiled_type_constraint};
2efc0af1 334
f5ee065f 335 return sub{
336 return !defined($_) || $check->($_);
337 };
338};
339
340sub _find_or_create_parameterized_type{
341 my($base, $param) = @_;
342
343 my $name = sprintf '%s[%s]', $base->name, $param->name;
344
345 $TYPE{$name} ||= do{
346 warn "#CREATE a Parameterized type for $name\n" if _DEBUG;
347
348 my $generator = $base->{constraint_generator};
349
350 if(!$generator){
351 confess("The $name constraint cannot be used, because $param doesn't subtype from a parameterizable type");
2efc0af1 352 }
f5ee065f 353
354 Mouse::Meta::TypeConstraint->new(
355 name => $name,
356 parent => $base,
357 constraint => $generator->($param),
358
359 type => 'Parameterized',
360 );
361 }
362}
363sub _find_or_create_union_type{
364 my @types = map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
365
366 my $name = join '|', map{ $_->name } @types;
367
368 $TYPE{$name} ||= do{
369 warn "# CREATE a Union type for ", Mouse::Util::english_list(@types),"\n" if _DEBUG;
370
e98220ab 371 my @checks = map{ $_->{_compiled_type_constraint} } @types;
f5ee065f 372 my $check = sub{
e98220ab 373 foreach my $c(@checks){
374 return 1 if $c->($_[0]);
f5ee065f 375 }
376 return 0;
377 };
378
379 return Mouse::Meta::TypeConstraint->new(
380 name => $name,
381 _compiled_type_constraint => $check,
382 type_constraints => \@types,
383
384 type => 'Union',
385 );
386 };
387}
388
389# The type parser
390sub _parse_type{
391 my($spec, $start) = @_;
392
393 my @list;
394 my $subtype;
395
396 my $len = length $spec;
397 my $i;
398
399 for($i = $start; $i < $len; $i++){
400 my $char = substr($spec, $i, 1);
401
402 if($char eq '['){
403 my $base = _find_or_create_regular_type( substr($spec, $start, $i - $start))
404 or return;
405
406 ($i, $subtype) = _parse_type($spec, $i+1)
407 or return;
408 $start = $i+1; # reset
409
410 push @list, _find_or_create_parameterized_type($base => $subtype);
321e5271 411 }
f5ee065f 412 elsif($char eq ']'){
413 $len = $i+1;
414 last;
321e5271 415 }
f5ee065f 416 elsif($char eq '|'){
417 my $type = _find_or_create_regular_type( substr($spec, $start, $i - $start))
418 or return;
419
420 push @list, $type;
421
422 ($i, $subtype) = _parse_type($spec, $i+1)
423 or return;
424
425 $start = $i+1; # reset
426
427 push @list, $subtype;
321e5271 428 }
429 }
f5ee065f 430 if($i - $start){
431 push @list, _find_or_create_regular_type(substr $spec, $start, $i - $start);
432 }
321e5271 433
f5ee065f 434 if(@list == 0){
435 return;
436 }
437 elsif(@list == 1){
438 return ($len, $list[0]);
993e62a7 439 }
440 else{
f5ee065f 441 return ($len, _find_or_create_union_type(@list));
993e62a7 442 }
321e5271 443}
444
f5ee065f 445
446sub find_type_constraint {
447 my($spec) = @_;
e98220ab 448 return $spec if blessed($spec) && $spec->isa('Mouse::Meta::TypeConstraint');
f5ee065f 449
450 $spec =~ s/\s+//g;
451 return $TYPE{$spec};
2efc0af1 452}
453
f5ee065f 454sub find_or_parse_type_constraint {
455 my($spec) = @_;
e98220ab 456 return $spec if blessed($spec) && $spec->isa('Mouse::Meta::TypeConstraint');
9c85e9dc 457
f5ee065f 458 $spec =~ s/\s+//g;
459 return $TYPE{$spec} || do{
460 my($pos, $type) = _parse_type($spec, 0);
461 $type;
462 };
463}
321e5271 464
f5ee065f 465sub find_or_create_does_type_constraint{
466 my $type = find_or_parse_type_constriant(@_) || role_type(@_);
94593ae8 467
f5ee065f 468 if($type->{type} && $type->{type} ne 'Role'){
469 Carp::cluck("$type is not a role type");
321e5271 470 }
f5ee065f 471 return $type;
472}
473
474sub find_or_create_isa_type_constraint {
475 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 476}
477
d60c78b9 4781;
479
6feb83f1 480__END__
481
482=head1 NAME
483
5893ee36 484Mouse::Util::TypeConstraints - Type constraint system for Mouse
485
486=head2 SYNOPSIS
487
488 use Mouse::Util::TypeConstraints;
489
490 subtype 'Natural'
491 => as 'Int'
492 => where { $_ > 0 };
493
494 subtype 'NaturalLessThanTen'
495 => as 'Natural'
496 => where { $_ < 10 }
497 => message { "This number ($_) is not less than ten!" };
498
499 coerce 'Num'
500 => from 'Str'
501 => via { 0+$_ };
502
503 enum 'RGBColors' => qw(red green blue);
504
505 no Mouse::Util::TypeConstraints;
506
507=head1 DESCRIPTION
508
509This module provides Mouse with the ability to create custom type
510constraints to be used in attribute definition.
511
512=head2 Important Caveat
513
514This is B<NOT> a type system for Perl 5. These are type constraints,
515and they are not used by Mouse unless you tell it to. No type
516inference is performed, expressions are not typed, etc. etc. etc.
517
518A type constraint is at heart a small "check if a value is valid"
519function. A constraint can be associated with an attribute. This
520simplifies parameter validation, and makes your code clearer to read,
521because you can refer to constraints by name.
522
523=head2 Slightly Less Important Caveat
524
525It is B<always> a good idea to quote your type names.
526
527This prevents Perl from trying to execute the call as an indirect
528object call. This can be an issue when you have a subtype with the
529same name as a valid class.
530
531For instance:
532
533 subtype DateTime => as Object => where { $_->isa('DateTime') };
534
535will I<just work>, while this:
536
537 use DateTime;
538 subtype DateTime => as Object => where { $_->isa('DateTime') };
539
540will fail silently and cause many headaches. The simple way to solve
541this, as well as future proof your subtypes from classes which have
542yet to have been created, is to quote the type name:
543
544 use DateTime;
545 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
546
547=head2 Default Type Constraints
548
549This module also provides a simple hierarchy for Perl 5 types, here is
550that hierarchy represented visually.
551
552 Any
553 Item
554 Bool
555 Maybe[`a]
556 Undef
557 Defined
558 Value
559 Num
560 Int
561 Str
562 ClassName
563 RoleName
564 Ref
565 ScalarRef
566 ArrayRef[`a]
567 HashRef[`a]
568 CodeRef
569 RegexpRef
570 GlobRef
571 FileHandle
572 Object
5893ee36 573
574B<NOTE:> Any type followed by a type parameter C<[`a]> can be
575parameterized, this means you can say:
576
577 ArrayRef[Int] # an array of integers
578 HashRef[CodeRef] # a hash of str to CODE ref mappings
579 Maybe[Str] # value may be a string, may be undefined
580
581If Mouse finds a name in brackets that it does not recognize as an
582existing type, it assumes that this is a class name, for example
583C<ArrayRef[DateTime]>.
584
585B<NOTE:> Unless you parameterize a type, then it is invalid to include
586the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
587name, I<not> as a parameterization of C<ArrayRef>.
588
589B<NOTE:> The C<Undef> type constraint for the most part works
590correctly now, but edge cases may still exist, please use it
591sparingly.
592
593B<NOTE:> The C<ClassName> type constraint does a complex package
594existence check. This means that your class B<must> be loaded for this
595type constraint to pass.
596
597B<NOTE:> The C<RoleName> constraint checks a string is a I<package
598name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
599constraint checks that an I<object does> the named role.
600
601=head2 Type Constraint Naming
602
603Type name declared via this module can only contain alphanumeric
604characters, colons (:), and periods (.).
605
606Since the types created by this module are global, it is suggested
607that you namespace your types just as you would namespace your
608modules. So instead of creating a I<Color> type for your
609B<My::Graphics> module, you would call the type
610I<My::Graphics::Types::Color> instead.
611
612=head2 Use with Other Constraint Modules
613
614This module can play nicely with other constraint modules with some
615slight tweaking. The C<where> clause in types is expected to be a
616C<CODE> reference which checks it's first argument and returns a
617boolean. Since most constraint modules work in a similar way, it
618should be simple to adapt them to work with Mouse.
619
620For instance, this is how you could use it with
621L<Declare::Constraints::Simple> to declare a completely new type.
622
623 type 'HashOfArrayOfObjects',
624 {
625 where => IsHashRef(
626 -keys => HasLength,
627 -values => IsArrayRef(IsObject)
628 )
629 };
630
631Here is an example of using L<Test::Deep> and it's non-test
632related C<eq_deeply> function.
633
634 type 'ArrayOfHashOfBarsAndRandomNumbers'
635 => where {
636 eq_deeply($_,
637 array_each(subhashof({
638 bar => isa('Bar'),
639 random_number => ignore()
640 })))
641 };
6feb83f1 642
643=head1 METHODS
644
645=head2 optimized_constraints -> HashRef[CODE]
646
647Returns the simple type constraints that Mouse understands.
648
c91d12e0 649=head1 FUNCTIONS
650
651=over 4
652
1820fffe 653=item C<< subtype 'Name' => as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 654
1820fffe 655=item C<< subtype as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 656
1820fffe 657=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 658
1820fffe 659=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 660
1820fffe 661=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
662
663=back
664
665=over 4
666
667=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 668
669=back
670
5893ee36 671=head1 THANKS
672
1820fffe 673Much of this documentation was taken from C<Moose::Util::TypeConstraints>
674
675=head1 SEE ALSO
676
677L<Moose::Util::TypeConstraints>
5893ee36 678
6feb83f1 679=cut
680
681