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