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