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