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