add some more explanation to the ::Delta entries
[gitmo/Moose.git] / lib / Moose / Meta / TypeConstraint / Union.pm
CommitLineData
8ee73eeb 1
2package Moose::Meta::TypeConstraint::Union;
3
4use strict;
5use warnings;
6use metaclass;
7
3726f905 8use Moose::Meta::TypeCoercion::Union;
9
09532816 10use List::MoreUtils qw(all);
1aae641c 11use List::Util qw(first);
12
d67145ed 13use base 'Moose::Meta::TypeConstraint';
14
8ee73eeb 15__PACKAGE__->meta->add_attribute('type_constraints' => (
16 accessor => 'type_constraints',
dc2b7cc8 17 default => sub { [] },
18 Class::MOP::_definition_context(),
8ee73eeb 19));
20
d03bd989 21sub new {
3726f905 22 my ($class, %options) = @_;
816ef2e2 23
24 my $name = join '|' => sort { $a cmp $b }
25 map { $_->name } @{ $options{type_constraints} };
26
3726f905 27 my $self = $class->SUPER::new(
816ef2e2 28 name => $name,
29 %options,
3726f905 30 );
816ef2e2 31
3726f905 32 $self->_set_constraint(sub { $self->check($_[0]) });
4beacd39 33
70f676ca 34 return $self;
35}
36
37# XXX - this is a rather gross implementation of laziness for the benefit of
38# MX::Types. If we try to call ->has_coercion on the objects during object
39# construction, this does not work when defining a recursive constraint with
40# MX::Types.
41sub coercion {
42 my $self = shift;
43
44 return $self->{coercion} if exists $self->{coercion};
45
5c56b608 46 # Using any instead of grep here causes a weird error with some corner
47 # cases when MX::Types is in use. See RT #61001.
48 if ( grep { $_->has_coercion } @{ $self->type_constraints } ) {
70f676ca 49 return $self->{coercion} = Moose::Meta::TypeCoercion::Union->new(
50 type_constraint => $self );
51 }
52 else {
53 return $self->{coercion} = undef;
4beacd39 54 }
70f676ca 55}
4beacd39 56
70f676ca 57sub has_coercion {
58 return defined $_[0]->coercion;
8ee73eeb 59}
60
816ef2e2 61sub _actually_compile_type_constraint {
62 my $self = shift;
63
64 my @constraints = @{ $self->type_constraints };
65
66 return sub {
67 my $value = shift;
68 foreach my $type (@constraints) {
69 return 1 if $type->check($value);
70 }
71 return undef;
72 };
73}
74
7c047a36 75sub can_be_inlined {
09532816 76 my $self = shift;
77
e0250b7c 78 # This was originally done with all() from List::MoreUtils, but that
79 # caused some sort of bizarro parsing failure under 5.10.
80 for my $tc ( @{ $self->type_constraints } ) {
81 return 0 unless $tc->can_be_inlined;
82 }
83
84 return 1;
09532816 85}
86
87sub _inline_check {
88 my $self = shift;
89 my $val = shift;
90
576c9cfc 91 return '('
92 . (
93 join ' || ', map { '(' . $_->_inline_check($val) . ')' }
94 @{ $self->type_constraints }
95 )
96 . ')';
6f08424b 97}
816ef2e2 98
ca789903 99sub inline_environment {
100 my $self = shift;
101
102 return { map { %{ $_->inline_environment } }
103 @{ $self->type_constraints } };
104}
105
dabed765 106sub equals {
107 my ( $self, $type_or_name ) = @_;
108
109 my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name);
110
111 return unless $other->isa(__PACKAGE__);
112
113 my @self_constraints = @{ $self->type_constraints };
114 my @other_constraints = @{ $other->type_constraints };
115
116 return unless @self_constraints == @other_constraints;
117
118 # FIXME presort type constraints for efficiency?
119 constraint: foreach my $constraint ( @self_constraints ) {
120 for ( my $i = 0; $i < @other_constraints; $i++ ) {
121 if ( $constraint->equals($other_constraints[$i]) ) {
122 splice @other_constraints, $i, 1;
123 next constraint;
124 }
125 }
126 }
127
128 return @other_constraints == 0;
129}
130
c0841d0c 131sub parent {
dabed765 132 my $self = shift;
c0841d0c 133
134 my @tcs = @{ $self->type_constraints };
135
136 my $deepest
137 = ( sort { $a->_ancestor_count <=> $b->_ancestor_count } @tcs )[-1];
138
139 for my $parent ( $deepest->_collect_all_parents ) {
140 return $parent if all { $_->is_a_type_of($parent) } @tcs;
141 }
dabed765 142}
143
8ee73eeb 144sub validate {
3726f905 145 my ($self, $value) = @_;
8ee73eeb 146 my $message;
147 foreach my $type (@{$self->type_constraints}) {
148 my $err = $type->validate($value);
149 return unless defined $err;
150 $message .= ($message ? ' and ' : '') . $err
151 if defined $err;
152 }
d03bd989 153 return ($message . ' in (' . $self->name . ')') ;
8ee73eeb 154}
155
1aae641c 156sub find_type_for {
157 my ($self, $value) = @_;
158
159 return first { $_->check($value) } @{ $self->type_constraints };
160}
161
8ee73eeb 162sub is_a_type_of {
163 my ($self, $type_name) = @_;
a93d14d1 164
165 return all { $_->is_a_type_of($type_name) } @{ $self->type_constraints };
8ee73eeb 166}
167
168sub is_subtype_of {
169 my ($self, $type_name) = @_;
a93d14d1 170
171 return all { $_->is_subtype_of($type_name) } @{ $self->type_constraints };
8ee73eeb 172}
173
85a9908f 174sub create_child_type {
622c9332 175 my ( $self, %opts ) = @_;
176
177 my $constraint
178 = Moose::Meta::TypeConstraint->new( %opts, parent => $self );
179
9ceb576e 180 # if we have a type constraint union, and no
181 # type check, this means we are just aliasing
182 # the union constraint, which means we need to
183 # handle this differently.
184 # - SL
622c9332 185 if ( not( defined $opts{constraint} )
186 && $self->has_coercion ) {
187 $constraint->coercion(
188 Moose::Meta::TypeCoercion::Union->new(
189 type_constraint => $self,
190 )
191 );
9ceb576e 192 }
622c9332 193
9ceb576e 194 return $constraint;
195}
196
8ee73eeb 1971;
198
ad46f524 199# ABSTRACT: A union of Moose type constraints
200
8ee73eeb 201__END__
202
203=pod
204
39b3bc94 205=head1 DESCRIPTION
206
ae2f99ea 207This metaclass represents a union of type constraints. A union takes
208multiple type constraints, and is true if any one of its member
209constraints is true.
ecb59493 210
ae2f99ea 211=head1 INHERITANCE
ecb59493 212
ae2f99ea 213C<Moose::Meta::TypeConstraint::Union> is a subclass of
214L<Moose::Meta::TypeConstraint>.
ecb59493 215
39b3bc94 216=over 4
217
ae2f99ea 218=item B<< Moose::Meta::TypeConstraint::Union->new(%options) >>
39b3bc94 219
ae2f99ea 220This creates a new class type constraint based on the given
221C<%options>.
1b58cb9f 222
ae2f99ea 223It takes the same options as its parent. It also requires an
224additional option, C<type_constraints>. This is an array reference
225containing the L<Moose::Meta::TypeConstraint> objects that are the
226members of the union type. The C<name> option defaults to the names
227all of these member types sorted and then joined by a pipe (|).
dabed765 228
ae2f99ea 229The constructor sets the implementation of the constraint so that is
230simply calls C<check> on the newly created object.
ecb59493 231
ae2f99ea 232Finally, the constructor also makes sure that the object's C<coercion>
233attribute is a L<Moose::Meta::TypeCoercion::Union> object.
ecb59493 234
ae2f99ea 235=item B<< $constraint->type_constraints >>
ecb59493 236
ae2f99ea 237This returns the array reference of C<type_constraints> provided to
238the constructor.
39b3bc94 239
c0841d0c 240=item B<< $constraint->parent >>
39b3bc94 241
c0841d0c 242This returns the nearest common ancestor of all the components of the union.
39b3bc94 243
ae2f99ea 244=item B<< $constraint->check($value) >>
39b3bc94 245
ae2f99ea 246=item B<< $constraint->validate($value) >>
39b3bc94 247
ae2f99ea 248These two methods simply call the relevant method on each of the
249member type constraints in the union. If any type accepts the value,
250the value is valid.
39b3bc94 251
ae2f99ea 252With C<validate> the error message returned includes all of the error
253messages returned by the member type constraints.
39b3bc94 254
ae2f99ea 255=item B<< $constraint->equals($type_name_or_object) >>
39b3bc94 256
ae2f99ea 257A type is considered equal if it is also a union type, and the two
258unions have the same member types.
39b3bc94 259
1aae641c 260=item B<< $constraint->find_type_for($value) >>
261
262This returns the first member type constraint for which C<check($value)> is
263true, allowing you to determine which of the Union's member type constraints
264a given value matches.
265
ae2f99ea 266=item B<< $constraint->is_a_type_of($type_name_or_object) >>
ecb59493 267
a93d14d1 268This returns true if all of the member type constraints return true
ae2f99ea 269for the C<is_a_type_of> method.
ecb59493 270
ae2f99ea 271=item B<< $constraint->is_subtype_of >>
39b3bc94 272
a93d14d1 273This returns true if all of the member type constraints return true
ae2f99ea 274for the C<is_a_subtype_of> method.
0eec94be 275
ae2f99ea 276=item B<< $constraint->create_child_type(%options) >>
0eec94be 277
ae2f99ea 278This returns a new L<Moose::Meta::TypeConstraint> object with the type
279as its parent.
9ceb576e 280
39b3bc94 281=back
282
283=head1 BUGS
284
d4048ef3 285See L<Moose/BUGS> for details on reporting bugs.
39b3bc94 286
8ee73eeb 287=cut