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