Fix triggers
[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
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
20 )],
139d92d2 21);
22
718b5d9b 23our @CARP_NOT = qw(Mouse::Meta::Attribute);
24
cceb0e25 25my %TYPE;
4188b837 26
df448257 27# The root type
28$TYPE{Any} = Mouse::Meta::TypeConstraint->new(
29 name => 'Any',
30);
31
32my @builtins = (
33 # $name => $parent, $code,
34
35 # the base type
36 Item => 'Any', undef,
37
38 # the maybe[] type
39 Maybe => 'Item', undef,
40
41 # value types
42 Undef => 'Item', \&Undef,
43 Defined => 'Item', \&Defined,
44 Bool => 'Item', \&Bool,
45 Value => 'Defined', \&Value,
46 Str => 'Value', \&Str,
47 Num => 'Str', \&Num,
48 Int => 'Num', \&Int,
49
50 # ref types
51 Ref => 'Defined', \&Ref,
52 ScalarRef => 'Ref', \&ScalarRef,
53 ArrayRef => 'Ref', \&ArrayRef,
54 HashRef => 'Ref', \&HashRef,
55 CodeRef => 'Ref', \&CodeRef,
56 RegexpRef => 'Ref', \&RegexpRef,
57 GlobRef => 'Ref', \&GlobRef,
58
59 # object types
60 FileHandle => 'GlobRef', \&FileHandle,
61 Object => 'Ref', \&Object,
62
63 # special string types
64 ClassName => 'Str', \&ClassName,
65 RoleName => 'ClassName', \&RoleName,
66);
67
68
69while (my ($name, $parent, $code) = splice @builtins, 0, 3) {
70 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
71 name => $name,
72 parent => $TYPE{$parent},
73 optimized => $code,
74 );
75}
76
77# make it parametarizable
78
79$TYPE{Maybe} {constraint_generator} = \&_parameterize_Maybe_for;
80$TYPE{ArrayRef}{constraint_generator} = \&_parameterize_ArrayRef_for;
81$TYPE{HashRef} {constraint_generator} = \&_parameterize_HashRef_for;
82
83# sugars
84
ddbad0b1 85sub as ($) { (as => $_[0]) }
86sub where (&) { (where => $_[0]) }
87sub message (&) { (message => $_[0]) }
5d4810c1 88sub optimize_as (&) { (optimize_as => $_[0]) }
61a02a3a 89
73766a27 90sub from { @_ }
cd2b9201 91sub via (&) { $_[0] }
61a02a3a 92
df448257 93# type utilities
993e62a7 94
df448257 95sub optimized_constraints { # DEPRECATED
96 Carp::cluck('optimized_constraints() has been deprecated');
97 return \%TYPE;
98}
d3982c7e 99
df448257 100undef @builtins; # free the allocated memory
101@builtins = keys %TYPE; # reuse it
102sub list_all_builtin_type_constraints { @builtins }
d4571def 103
df448257 104sub list_all_type_constraints { keys %TYPE }
993e62a7 105
f5ee065f 106sub _create_type{
107 my $mode = shift;
108
73766a27 109 my $name;
f5ee065f 110 my %args;
73766a27 111
f5ee065f 112 if(@_ == 1 && ref $_[0]){ # @_ : { name => $name, where => ... }
113 %args = %{$_[0]};
73766a27 114 }
f5ee065f 115 elsif(@_ == 2 && ref $_[1]){ # @_ : $name => { where => ... }
73766a27 116 $name = $_[0];
f5ee065f 117 %args = %{$_[1]};
73766a27 118 }
f5ee065f 119 elsif(@_ % 2){ # @_ : $name => ( where => ... )
120 ($name, %args) = @_;
73766a27 121 }
f5ee065f 122 else{ # @_ : (name => $name, where => ...)
123 %args = @_;
73766a27 124 }
125
f5ee065f 126 if(!defined $name){
4f24c598 127 $name = $args{name};
cd2b9201 128 }
d4571def 129
f5ee065f 130 $args{name} = $name;
b8434acc 131 my $parent;
132 if($mode eq 'subtype'){
133 $parent = delete $args{as};
134 if(!$parent){
135 $parent = delete $args{name};
4f24c598 136 $name = undef;
b8434acc 137 }
138 }
7dbebb1b 139
4f24c598 140 if(defined $name){
df448257 141 # set 'package_defined_in' only if it is not a core package
142 my $this = $args{package_defined_in};
143 if(!$this){
144 $this = caller(1);
145 if($this !~ /\A Mouse \b/xms){
146 $args{package_defined_in} = $this;
147 }
148 }
149
150 if($TYPE{$name}){
151 my $that = $TYPE{$name}->{package_defined_in} || __PACKAGE__;
718b5d9b 152 ($this eq $that) or Carp::croak(
df448257 153 "The type constraint '$name' has already been created in $that and cannot be created again in $this"
154 );
4f24c598 155 }
156 }
157 else{
158 $args{name} = '__ANON__';
f5ee065f 159 }
d4571def 160
b8434acc 161 $args{constraint} = delete $args{where} if exists $args{where};
24410e3a 162 $args{optimized} = delete $args{optimized_as} if exists $args{optimized_as};
0d9fea22 163
f5ee065f 164 my $constraint;
165 if($mode eq 'subtype'){
24410e3a 166 $constraint = find_or_create_isa_type_constraint($parent)->create_child_type(%args);
7a50b450 167 }
168 else{
f5ee065f 169 $constraint = Mouse::Meta::TypeConstraint->new(%args);
73766a27 170 }
7dbebb1b 171
4f24c598 172 if(defined $name){
173 return $TYPE{$name} = $constraint;
174 }
175 else{
176 return $constraint;
177 }
f5ee065f 178}
7dbebb1b 179
f5ee065f 180sub type {
181 return _create_type('type', @_);
182}
d4571def 183
f5ee065f 184sub subtype {
185 return _create_type('subtype', @_);
4188b837 186}
187
139d92d2 188sub coerce {
ffbbf459 189 my $type_name = shift;
61a02a3a 190
ffbbf459 191 my $type = find_type_constraint($type_name)
718b5d9b 192 or Carp::croak("Cannot find type '$type_name', perhaps you forgot to load it.");
61a02a3a 193
ffbbf459 194 $type->_add_type_coercions(@_);
cd2b9201 195 return;
4188b837 196}
197
139d92d2 198sub class_type {
337e3b0c 199 my($name, $options) = @_;
200 my $class = $options->{class} || $name;
df448257 201
202 # ClassType
337e3b0c 203 return _create_type 'subtype', $name => (
ddbad0b1 204 as => 'Object',
e3540312 205 optimized_as => Mouse::Util::generate_isa_predicate_for($class),
337e3b0c 206 );
ecc6e3b1 207}
208
139d92d2 209sub role_type {
337e3b0c 210 my($name, $options) = @_;
211 my $role = $options->{role} || $name;
df448257 212
213 # RoleType
337e3b0c 214 return _create_type 'subtype', $name => (
1d5ecd5f 215 as => 'Object',
b7d1f970 216 optimized_as => sub { Scalar::Util::blessed($_[0]) && does_role($_[0], $role) },
61a02a3a 217 );
47f36c05 218}
219
ebe91068 220sub duck_type {
221 my($name, @methods);
222
223 if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){
224 $name = shift;
225 }
226
227 @methods = (@_ == 1 && ref($_[0]) eq 'ARRAY') ? @{$_[0]} : @_;
228
df448257 229 # DuckType
ebe91068 230 return _create_type 'type', $name => (
231 optimized_as => Mouse::Util::generate_can_predicate_for(\@methods),
ebe91068 232 );
233}
234
d44f0d03 235sub enum {
f5ee065f 236 my($name, %valid);
237
f152b099 238 if(!(@_ == 1 && ref($_[0]) eq 'ARRAY')){
239 $name = shift;
f5ee065f 240 }
f152b099 241
242 %valid = map{ $_ => undef } (@_ == 1 && ref($_[0]) eq 'ARRAY' ? @{$_[0]} : @_);
243
df448257 244 # EnumType
f5ee065f 245 return _create_type 'type', $name => (
246 optimized_as => sub{ defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]} },
f5ee065f 247 );
248}
249
250sub _find_or_create_regular_type{
718b5d9b 251 my($spec, $create) = @_;
f5ee065f 252
253 return $TYPE{$spec} if exists $TYPE{$spec};
01904723 254
718b5d9b 255 my $meta = Mouse::Util::get_metaclass_by_name($spec);
256
257 if(!defined $meta){
258 return $create ? class_type($spec) : undef;
259 }
01904723 260
f48920c1 261 if(Mouse::Util::is_a_metarole($meta)){
1d5ecd5f 262 return role_type($spec);
f5ee065f 263 }
264 else{
1d5ecd5f 265 return class_type($spec);
f5ee065f 266 }
d44f0d03 267}
268
f5ee065f 269sub _find_or_create_parameterized_type{
270 my($base, $param) = @_;
271
272 my $name = sprintf '%s[%s]', $base->name, $param->name;
273
b4d791ba 274 $TYPE{$name} ||= $base->parameterize($param, $name);
f5ee065f 275}
b4d791ba 276
f5ee065f 277sub _find_or_create_union_type{
718b5d9b 278 return if grep{ not defined } @_;
a2f1294a 279 my @types = sort map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
f5ee065f 280
30b28db3 281 my $name = join '|', @types;
f5ee065f 282
df448257 283 # UnionType
b4d791ba 284 $TYPE{$name} ||= Mouse::Meta::TypeConstraint->new(
285 name => $name,
286 type_constraints => \@types,
b4d791ba 287 );
f5ee065f 288}
289
290# The type parser
f5ee065f 291
718b5d9b 292# param : '[' type ']' | NOTHING
293sub _parse_param {
294 my($c) = @_;
f5ee065f 295
718b5d9b 296 if($c->{spec} =~ s/^\[//){
297 my $type = _parse_type($c, 1);
f5ee065f 298
718b5d9b 299 if($c->{spec} =~ s/^\]//){
300 return $type;
321e5271 301 }
718b5d9b 302 Carp::croak("Syntax error in type: missing right square bracket in '$c->{orig}'");
303 }
57f0e313 304
718b5d9b 305 return undef;
306}
f5ee065f 307
718b5d9b 308# name : [\w.:]+
309sub _parse_name {
310 my($c, $create) = @_;
f5ee065f 311
718b5d9b 312 if($c->{spec} =~ s/\A ([\w.:]+) //xms){
313 return _find_or_create_regular_type($1, $create);
314 }
315 Carp::croak("Syntax error in type: expect type name near '$c->{spec}' in '$c->{orig}'");
316}
f5ee065f 317
718b5d9b 318# single_type : name param
319sub _parse_single_type {
320 my($c, $create) = @_;
f5ee065f 321
718b5d9b 322 my $type = _parse_name($c, $create);
323 my $param = _parse_param($c);
29376895 324
718b5d9b 325 if(defined $type){
326 if(defined $param){
327 return _find_or_create_parameterized_type($type, $param);
29376895 328 }
718b5d9b 329 else {
330 return $type;
29376895 331 }
f5ee065f 332 }
718b5d9b 333 elsif(defined $param){
334 Carp::croak("Undefined type with parameter [$param] in '$c->{orig}'");
993e62a7 335 }
336 else{
718b5d9b 337 return undef;
993e62a7 338 }
321e5271 339}
340
718b5d9b 341# type : single_type ('|' single_type)*
342sub _parse_type {
343 my($c, $create) = @_;
344
345 my $type = _parse_single_type($c, $create);
346 if($c->{spec}){ # can be an union type
347 my @types;
348 while($c->{spec} =~ s/^\|//){
349 push @types, _parse_single_type($c, $create);
350 }
351 if(@types){
352 return _find_or_create_union_type($type, @types);
353 }
354 }
355 return $type;
356}
357
f5ee065f 358
359sub find_type_constraint {
360 my($spec) = @_;
f48920c1 361 return $spec if Mouse::Util::is_a_type_constraint($spec);
df448257 362 return undef if !defined $spec;
f5ee065f 363
364 $spec =~ s/\s+//g;
365 return $TYPE{$spec};
2efc0af1 366}
367
f5ee065f 368sub find_or_parse_type_constraint {
369 my($spec) = @_;
f48920c1 370 return $spec if Mouse::Util::is_a_type_constraint($spec);
df448257 371 return undef if !defined $spec;
9c85e9dc 372
f5ee065f 373 $spec =~ s/\s+//g;
374 return $TYPE{$spec} || do{
718b5d9b 375 my $context = {
376 spec => $spec,
377 orig => $spec,
378 };
379 my $type = _parse_type($context);
380
381 if($context->{spec}){
382 Carp::croak("Syntax error: extra elements '$context->{spec}' in '$context->{orig}'");
383 }
f5ee065f 384 $type;
385 };
386}
321e5271 387
f5ee065f 388sub find_or_create_does_type_constraint{
ddbad0b1 389 # XXX: Moose does not register a new role_type, but Mouse does.
bddbe49f 390 return find_or_parse_type_constraint(@_) || role_type(@_);
f5ee065f 391}
392
393sub find_or_create_isa_type_constraint {
ddbad0b1 394 # XXX: Moose does not register a new class_type, but Mouse does.
f5ee065f 395 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 396}
397
d60c78b9 3981;
6feb83f1 399__END__
400
401=head1 NAME
402
5893ee36 403Mouse::Util::TypeConstraints - Type constraint system for Mouse
404
a25ca8d6 405=head1 VERSION
406
80680661 407This document describes Mouse version 0.50_07
a25ca8d6 408
5893ee36 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
dba2e142 475 Any
5893ee36 476 Item
477 Bool
478 Maybe[`a]
479 Undef
480 Defined
481 Value
5893ee36 482 Str
dba2e142 483 Num
484 Int
485 ClassName
486 RoleName
5893ee36 487 Ref
488 ScalarRef
489 ArrayRef[`a]
490 HashRef[`a]
491 CodeRef
492 RegexpRef
493 GlobRef
dba2e142 494 FileHandle
5893ee36 495 Object
5893ee36 496
497B<NOTE:> Any type followed by a type parameter C<[`a]> can be
498parameterized, this means you can say:
499
500 ArrayRef[Int] # an array of integers
501 HashRef[CodeRef] # a hash of str to CODE ref mappings
502 Maybe[Str] # value may be a string, may be undefined
503
504If Mouse finds a name in brackets that it does not recognize as an
505existing type, it assumes that this is a class name, for example
506C<ArrayRef[DateTime]>.
507
5893ee36 508B<NOTE:> The C<Undef> type constraint for the most part works
509correctly now, but edge cases may still exist, please use it
510sparingly.
511
512B<NOTE:> The C<ClassName> type constraint does a complex package
513existence check. This means that your class B<must> be loaded for this
514type constraint to pass.
515
516B<NOTE:> The C<RoleName> constraint checks a string is a I<package
517name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
518constraint checks that an I<object does> the named role.
519
520=head2 Type Constraint Naming
521
522Type name declared via this module can only contain alphanumeric
523characters, colons (:), and periods (.).
524
525Since the types created by this module are global, it is suggested
526that you namespace your types just as you would namespace your
527modules. So instead of creating a I<Color> type for your
528B<My::Graphics> module, you would call the type
529I<My::Graphics::Types::Color> instead.
530
531=head2 Use with Other Constraint Modules
532
533This module can play nicely with other constraint modules with some
534slight tweaking. The C<where> clause in types is expected to be a
535C<CODE> reference which checks it's first argument and returns a
536boolean. Since most constraint modules work in a similar way, it
537should be simple to adapt them to work with Mouse.
538
539For instance, this is how you could use it with
540L<Declare::Constraints::Simple> to declare a completely new type.
541
542 type 'HashOfArrayOfObjects',
543 {
544 where => IsHashRef(
545 -keys => HasLength,
546 -values => IsArrayRef(IsObject)
547 )
548 };
549
550Here is an example of using L<Test::Deep> and it's non-test
551related C<eq_deeply> function.
552
553 type 'ArrayOfHashOfBarsAndRandomNumbers'
554 => where {
555 eq_deeply($_,
556 array_each(subhashof({
557 bar => isa('Bar'),
558 random_number => ignore()
559 })))
560 };
6feb83f1 561
562=head1 METHODS
563
24410e3a 564=head2 C<< list_all_builtin_type_constraints -> (Names) >>
6feb83f1 565
24410e3a 566Returns the names of builtin type constraints.
567
568=head2 C<< list_all_type_constraints -> (Names) >>
569
570Returns the names of all the type constraints.
6feb83f1 571
c91d12e0 572=head1 FUNCTIONS
573
574=over 4
575
c9cc6884 576=item C<< type $name => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 577
c9cc6884 578=item C<< subtype $name => as $parent => where { } ... -> Mouse::Meta::TypeConstraint >>
579
580=item C<< subtype as $parent => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 581
1820fffe 582=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 583
1820fffe 584=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 585
c9cc6884 586=item C<< duck_type($name, @methods | \@methods) -> Mouse::Meta::TypeConstraint >>
587
588=item C<< duck_type(\@methods) -> Mouse::Meta::TypeConstraint >>
589
590=item C<< enum($name, @values | \@values) -> Mouse::Meta::TypeConstraint >>
591
1820fffe 592=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
593
87f1f3d2 594=item C<< coerce $type => from $another_type, via { }, ... >>
595
1820fffe 596=back
597
598=over 4
599
600=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 601
602=back
603
5893ee36 604=head1 THANKS
605
1820fffe 606Much of this documentation was taken from C<Moose::Util::TypeConstraints>
607
608=head1 SEE ALSO
609
610L<Moose::Util::TypeConstraints>
5893ee36 611
6feb83f1 612=cut
613
614