Add tests for or-combination operator
[gitmo/Mouse.git] / lib / Mouse / Meta / TypeConstraint.pm
CommitLineData
684db121 1package Mouse::Meta::TypeConstraint;
bc69ee88 2use Mouse::Util qw(:meta); # enables strict and warnings
9c85e9dc 3
f5ee065f 4use overload
c489a477 5 'bool' => sub { 1 }, # always true
6
93540011 7 '""' => sub { $_[0]->name }, # stringify to tc name
8adc0b23 8
9 '|' => sub { # or-combination
10 require Mouse::Util::TypeConstraints;
11 return Mouse::Util::TypeConstraints::find_or_parse_type_constraint(
12 "$_[0] | $_[1]",
13 );
14 },
15
f5ee065f 16 fallback => 1;
684db121 17
ca352580 18use Carp ();
6d28c5cf 19
f5ee065f 20my $null_check = sub { 1 };
21
684db121 22sub new {
f5ee065f 23 my($class, %args) = @_;
24
25 $args{name} = '__ANON__' if !defined $args{name};
684db121 26
3b89ea91 27 my $check = delete $args{optimized};
28
29 if($args{_compiled_type_constraint}){
0126c27c 30 Carp::cluck("'_compiled_type_constraint' has been deprecated, use 'optimized' instead")
31 if _MOUSE_VERBOSE;
3b89ea91 32
0126c27c 33 $check = $args{_compiled_type_constraint};
3b89ea91 34 }
35
36 if($check){
37 $args{hand_optimized_type_constraint} = $check;
38 $args{compiled_type_constraint} = $check;
39 }
40
41 $check = $args{constraint};
f5ee065f 42
f5ee065f 43 if(defined($check) && ref($check) ne 'CODE'){
ca352580 44 Carp::confess("Constraint for $args{name} is not a CODE reference");
f5ee065f 45 }
46
3b89ea91 47 $args{package_defined_in} ||= caller;
48
f5ee065f 49 my $self = bless \%args, $class;
3b89ea91 50 $self->compile_type_constraint() if !$self->{hand_optimized_type_constraint};
f5ee065f 51
ffbbf459 52 if($self->{type_constraints}){ # Union
53 my @coercions;
54 foreach my $type(@{$self->{type_constraints}}){
55 if($type->has_coercion){
56 push @coercions, $type;
57 }
58 }
59 if(@coercions){
60 $self->{_compiled_type_coercion} = sub {
61 my($thing) = @_;
62 foreach my $type(@coercions){
63 my $value = $type->coerce($thing);
64 return $value if $self->check($value);
65 }
66 return $thing;
67 };
68 }
69 }
70
f5ee065f 71 return $self;
72}
73
74sub create_child_type{
75 my $self = shift;
e98220ab 76 # XXX: FIXME
77 return ref($self)->new(
3b89ea91 78 # a child inherits its parent's attributes
79 %{$self},
80
81 # but does not inherit 'compiled_type_constraint' and 'hand_optimized_type_constraint'
82 compiled_type_constraint => undef,
83 hand_optimized_type_constraint => undef,
84
85 # and is given child-specific args, of course.
86 @_,
87
88 # and its parent
89 parent => $self,
e98220ab 90 );
684db121 91}
92
f5ee065f 93
3b89ea91 94sub compile_type_constraint{
f5ee065f 95 my($self) = @_;
96
97 # add parents first
98 my @checks;
99 for(my $parent = $self->parent; defined $parent; $parent = $parent->parent){
3b89ea91 100 if($parent->{hand_optimized_type_constraint}){
c4b28dd2 101 unshift @checks, $parent->{hand_optimized_type_constraint};
3b89ea91 102 last; # a hand optimized constraint must include all the parents
103 }
104 elsif($parent->{constraint}){
c4b28dd2 105 unshift @checks, $parent->{constraint};
f5ee065f 106 }
107 }
3b89ea91 108
f5ee065f 109 # then add child
110 if($self->{constraint}){
111 push @checks, $self->{constraint};
112 }
113
3b89ea91 114 if($self->{type_constraints}){ # Union
115 my @types = map{ $_->_compiled_type_constraint } @{ $self->{type_constraints} };
116 push @checks, sub{
117 foreach my $c(@types){
118 return 1 if $c->($_[0]);
119 }
120 return 0;
121 };
122 }
123
f5ee065f 124 if(@checks == 0){
3b89ea91 125 $self->{compiled_type_constraint} = $null_check;
f5ee065f 126 }
127 elsif(@checks == 1){
128 my $c = $checks[0];
3b89ea91 129 $self->{compiled_type_constraint} = sub{
f5ee065f 130 my(@args) = @_;
131 local $_ = $args[0];
132 return $c->(@args);
133 };
134 }
135 else{
3b89ea91 136 $self->{compiled_type_constraint} = sub{
f5ee065f 137 my(@args) = @_;
138 local $_ = $args[0];
139 foreach my $c(@checks){
140 return undef if !$c->(@args);
141 }
142 return 1;
143 };
144 }
3b89ea91 145 return;
90fe520e 146}
147
ffbbf459 148sub _add_type_coercions{
149 my $self = shift;
150
151 my $coercions = ($self->{_coercion_map} ||= []);
152 my %has = map{ $_->[0] => undef } @{$coercions};
153
154 for(my $i = 0; $i < @_; $i++){
155 my $from = $_[ $i];
156 my $action = $_[++$i];
157
158 if(exists $has{$from}){
ca352580 159 Carp::confess("A coercion action already exists for '$from'");
ffbbf459 160 }
161
162 my $type = Mouse::Util::TypeConstraints::find_or_parse_type_constraint($from)
ca352580 163 or Carp::confess("Could not find the type constraint ($from) to coerce from");
ffbbf459 164
165 push @{$coercions}, [ $type => $action ];
166 }
167
168 # compile
169 if(exists $self->{type_constraints}){ # union type
ca352580 170 Carp::confess("Cannot add additional type coercions to Union types");
ffbbf459 171 }
172 else{
173 $self->{_compiled_type_coercion} = sub {
174 my($thing) = @_;\r
175 foreach my $pair (@{$coercions}) {\r
176 #my ($constraint, $converter) = @$pair;\r
177 if ($pair->[0]->check($thing)) {\r
178 local $_ = $thing;
179 return $pair->[1]->($thing);
180 }\r
181 }\r
182 return $thing;\r
183 };
184 }
185 return;
186}
187
feb0e21b 188sub check {
189 my $self = shift;
ffbbf459 190 return $self->_compiled_type_constraint->(@_);
191}
192
193sub coerce {
194 my $self = shift;
ffbbf459 195
196 return $_[0] if $self->_compiled_type_constraint->(@_);
197
93540011 198 my $coercion = $self->_compiled_type_coercion;
199 return $coercion ? $coercion->(@_) : $_[0];
feb0e21b 200}
201
202sub get_message {
203 my ($self, $value) = @_;
204 if ( my $msg = $self->message ) {
205 local $_ = $value;
206 return $msg->($value);
207 }
208 else {
209 $value = ( defined $value ? overload::StrVal($value) : 'undef' );
210 return "Validation failed for '$self' failed with value $value";
211 }
212}
213
214sub is_a_type_of{
215 my($self, $other) = @_;
216
217 # ->is_a_type_of('__ANON__') is always false
ca352580 218 return 0 if !ref($other) && $other eq '__ANON__';
feb0e21b 219
220 (my $other_name = $other) =~ s/\s+//g;
221
222 return 1 if $self->name eq $other_name;
223
224 if(exists $self->{type_constraints}){ # union
225 foreach my $type(@{$self->{type_constraints}}){
226 return 1 if $type->name eq $other_name;
227 }
228 }
229
230 for(my $parent = $self->parent; defined $parent; $parent = $parent->parent){
231 return 1 if $parent->name eq $other_name;
232 }
233
234 return 0;
235}
236
237
684db121 2381;
239__END__
240
241=head1 NAME
242
1820fffe 243Mouse::Meta::TypeConstraint - The Mouse Type Constraint metaclass
684db121 244
a25ca8d6 245=head1 VERSION
246
065f79e7 247This document describes Mouse version 0.40_02
a25ca8d6 248
684db121 249=head1 DESCRIPTION
250
251For the most part, the only time you will ever encounter an
252instance of this class is if you are doing some serious deep
253introspection. This API should not be considered final, but
254it is B<highly unlikely> that this will matter to a regular
255Mouse user.
256
257Don't use this.
258
259=head1 METHODS
260
261=over 4
262
263=item B<new>
264
265=item B<name>
266
267=back
268
1820fffe 269=head1 SEE ALSO
270
271L<Moose::Meta::TypeConstraint>
272
684db121 273=cut
274