Add an error check
[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
684db121 4sub new {
184f8f53 5 my $class = shift;
6 my %args = @_ == 1 ? %{$_[0]} : @_;
f5ee065f 7
8 $args{name} = '__ANON__' if !defined $args{name};
684db121 9
5a592ad7 10 if($args{parent}) {
11 %args = (%{$args{parent}}, %args);
12 # a child type must not inherit 'compiled_type_constraint'
13 # and 'hand_optimized_type_constraint' from the parent
14 delete $args{compiled_type_constraint};
15 delete $args{hand_optimized_type_constraint};
16 }
17
18 my $check;
3b89ea91 19
5a592ad7 20 if($check = delete $args{optimized}) {
3b89ea91 21 $args{hand_optimized_type_constraint} = $check;
22 $args{compiled_type_constraint} = $check;
23 }
5a592ad7 24 elsif(my $param = $args{type_parameter}) {
25 my $generator = $args{constraint_generator}
26 || $class->throw_error("The $args{name} constraint cannot be used,"
27 . " because $param doesn't subtype from a parameterizable type");
28 # it must be 'constraint'
29 $check = $args{constraint} = $generator->($param);
30 }
31 else {
32 $check = $args{constraint};
33 }
f5ee065f 34
f5ee065f 35 if(defined($check) && ref($check) ne 'CODE'){
3a29a080 36 $class->throw_error(
37 "Constraint for $args{name} is not a CODE reference");
f5ee065f 38 }
39
40 my $self = bless \%args, $class;
3a29a080 41 $self->compile_type_constraint()
5a592ad7 42 if !$args{hand_optimized_type_constraint};
f5ee065f 43
5a592ad7 44 if($args{type_constraints}) {
45 $self->_compile_union_type_coercion();
46 }
f5ee065f 47 return $self;
48}
49
c5940bae 50sub create_child_type {
f5ee065f 51 my $self = shift;
c5940bae 52 return ref($self)->new(@_, parent => $self);
684db121 53}
54
4c99ea95 55sub name;
56sub parent;
57sub message;
58sub has_coercion;
2b82d75d 59
c7576321 60sub check;
61
2b82d75d 62sub type_parameter;
63sub __is_parameterized;
64
4c99ea95 65sub _compiled_type_constraint;
66sub _compiled_type_coercion;
67
68sub compile_type_constraint;
69
c7576321 70
3a29a080 71sub _add_type_coercions { # ($self, @pairs)
ffbbf459 72 my $self = shift;
73
cc131889 74 my $coercions = ($self->{coercion_map} ||= []);
ffbbf459 75 my %has = map{ $_->[0] => undef } @{$coercions};
76
77 for(my $i = 0; $i < @_; $i++){
78 my $from = $_[ $i];
79 my $action = $_[++$i];
80
81 if(exists $has{$from}){
c7576321 82 $self->throw_error("A coercion action already exists for '$from'");
ffbbf459 83 }
84
85 my $type = Mouse::Util::TypeConstraints::find_or_parse_type_constraint($from)
3a29a080 86 or $self->throw_error(
87 "Could not find the type constraint ($from) to coerce from");
ffbbf459 88
89 push @{$coercions}, [ $type => $action ];
90 }
91
92 # compile
93 if(exists $self->{type_constraints}){ # union type
3a29a080 94 $self->throw_error(
95 "Cannot add additional type coercions to Union types");
ffbbf459 96 }
97 else{
536e3c33 98 $self->_compile_type_coercion();
99 }
100 return;
101}
102
103sub _compile_type_coercion {
104 my($self) = @_;
105
cc131889 106 my @coercions = @{$self->{coercion_map}};
536e3c33 107
108 $self->{_compiled_type_coercion} = sub {
109 my($thing) = @_;
110 foreach my $pair (@coercions) {
111 #my ($constraint, $converter) = @$pair;
112 if ($pair->[0]->check($thing)) {
113 local $_ = $thing;
114 return $pair->[1]->($thing);
115 }
116 }
117 return $thing;
118 };
119 return;
120}
121
122sub _compile_union_type_coercion {
123 my($self) = @_;
124
125 my @coercions;
126 foreach my $type(@{$self->{type_constraints}}){
127 if($type->has_coercion){
128 push @coercions, $type;
129 }
130 }
131 if(@coercions){
ffbbf459 132 $self->{_compiled_type_coercion} = sub {
536e3c33 133 my($thing) = @_;
134 foreach my $type(@coercions){
135 my $value = $type->coerce($thing);
136 return $value if $self->check($value);
137 }
138 return $thing;
ffbbf459 139 };
140 }
141 return;
142}
143
ffbbf459 144sub coerce {
145 my $self = shift;
ffbbf459 146
de0d4152 147 my $coercion = $self->_compiled_type_coercion;
148 if(!$coercion){
c7576321 149 $self->throw_error("Cannot coerce without a type coercion");
de0d4152 150 }
151
c7576321 152 return $_[0] if $self->check(@_);
ffbbf459 153
de0d4152 154 return $coercion->(@_);
feb0e21b 155}
156
157sub get_message {
158 my ($self, $value) = @_;
159 if ( my $msg = $self->message ) {
160 local $_ = $value;
161 return $msg->($value);
162 }
163 else {
6e647cac 164 if(not defined $value) {
165 $value = 'undef';
166 }
167 elsif( ref($value) && defined(&overload::StrVal) ) {
168 $value = overload::StrVal($value);
169 }
537873b0 170 return "Validation failed for '$self' with value $value";
feb0e21b 171 }
172}
173
174sub is_a_type_of{
175 my($self, $other) = @_;
176
177 # ->is_a_type_of('__ANON__') is always false
ca352580 178 return 0 if !ref($other) && $other eq '__ANON__';
feb0e21b 179
180 (my $other_name = $other) =~ s/\s+//g;
181
182 return 1 if $self->name eq $other_name;
183
184 if(exists $self->{type_constraints}){ # union
3a29a080 185 foreach my $type(@{$self->{type_constraints}}) {
feb0e21b 186 return 1 if $type->name eq $other_name;
187 }
188 }
189
3a29a080 190 for(my $p = $self->parent; defined $p; $p = $p->parent) {
191 return 1 if $p->name eq $other_name;
feb0e21b 192 }
193
194 return 0;
195}
196
b4d791ba 197# See also Moose::Meta::TypeConstraint::Parameterizable
198sub parameterize{
199 my($self, $param, $name) = @_;
200
201 if(!ref $param){
202 require Mouse::Util::TypeConstraints;
203 $param = Mouse::Util::TypeConstraints::find_or_create_isa_type_constraint($param);
204 }
205
206 $name ||= sprintf '%s[%s]', $self->name, $param->name;
b4d791ba 207 return Mouse::Meta::TypeConstraint->new(
fc83f4cf 208 name => $name,
209 parent => $self,
210 type_parameter => $param,
b4d791ba 211 );
212}
feb0e21b 213
78508064 214sub assert_valid {
215 my ($self, $value) = @_;
216
c7576321 217 if(!$self->check($value)){
218 $self->throw_error($self->get_message($value));
78508064 219 }
220 return 1;
221}
222
6e647cac 223sub _as_string { $_[0]->name } # overload ""
f6c81f00 224sub _identity; # overload 0+
6e647cac 225
226sub _unite { # overload infix:<|>
227 my($lhs, $rhs) = @_;
228 require Mouse::Util::TypeConstraints;
229 return Mouse::Util::TypeConstraints::find_or_parse_type_constraint(
230 " $lhs | $rhs",
231 );
232}
233
684db121 2341;
235__END__
236
237=head1 NAME
238
1820fffe 239Mouse::Meta::TypeConstraint - The Mouse Type Constraint metaclass
684db121 240
a25ca8d6 241=head1 VERSION
242
b0d52f03 243This document describes Mouse version 0.73
a25ca8d6 244
684db121 245=head1 DESCRIPTION
246
3a29a080 247This class represents a type constraint, including built-in
248type constraints, union type constraints, parameterizable/
249parameterized type constraints, as well as custom type
250constraints
684db121 251
252=head1 METHODS
253
ba05bb84 254=over
3a29a080 255
256=item C<< Mouse::Meta::TypeConstraint->new(%options) >>
257
258=item C<< $constraint->name >>
259
260=item C<< $constraint->parent >>
261
262=item C<< $constraint->constraint >>
263
264=item C<< $constraint->has_coercion >>
265
266=item C<< $constraint->message >>
267
268=item C<< $constraint->is_a_subtype_of($name or $object) >>
269
270=item C<< $constraint->coerce($value) >>
271
272=item C<< $constraint->check($value) >>
273
274=item C<< $constraint->assert_valid($value) >>
684db121 275
3a29a080 276=item C<< $constraint->get_message($value) >>
684db121 277
3a29a080 278=item C<< $constraint->create_child_type(%options) >>
684db121 279
280=back
281
1820fffe 282=head1 SEE ALSO
283
284L<Moose::Meta::TypeConstraint>
285
684db121 286=cut
287