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