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