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