RT#83929: fix memory leak in union types
[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
c0570459 32 $self->_set_constraint( $self->_compiled_type_constraint );
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
db730b31 134 my ($first, @rest) = @{ $self->type_constraints };
c0841d0c 135
db730b31 136 for my $parent ( $first->_collect_all_parents ) {
137 return $parent if all { $_->is_a_type_of($parent) } @rest;
c0841d0c 138 }
86036fe3 139
140 return;
dabed765 141}
142
8ee73eeb 143sub validate {
3726f905 144 my ($self, $value) = @_;
8ee73eeb 145 my $message;
146 foreach my $type (@{$self->type_constraints}) {
147 my $err = $type->validate($value);
148 return unless defined $err;
149 $message .= ($message ? ' and ' : '') . $err
150 if defined $err;
151 }
d03bd989 152 return ($message . ' in (' . $self->name . ')') ;
8ee73eeb 153}
154
1aae641c 155sub find_type_for {
156 my ($self, $value) = @_;
157
158 return first { $_->check($value) } @{ $self->type_constraints };
159}
160
8ee73eeb 161sub is_a_type_of {
162 my ($self, $type_name) = @_;
a93d14d1 163
164 return all { $_->is_a_type_of($type_name) } @{ $self->type_constraints };
8ee73eeb 165}
166
167sub is_subtype_of {
168 my ($self, $type_name) = @_;
a93d14d1 169
170 return all { $_->is_subtype_of($type_name) } @{ $self->type_constraints };
8ee73eeb 171}
172
85a9908f 173sub create_child_type {
622c9332 174 my ( $self, %opts ) = @_;
175
176 my $constraint
177 = Moose::Meta::TypeConstraint->new( %opts, parent => $self );
178
9ceb576e 179 # if we have a type constraint union, and no
180 # type check, this means we are just aliasing
181 # the union constraint, which means we need to
182 # handle this differently.
183 # - SL
622c9332 184 if ( not( defined $opts{constraint} )
185 && $self->has_coercion ) {
186 $constraint->coercion(
187 Moose::Meta::TypeCoercion::Union->new(
188 type_constraint => $self,
189 )
190 );
9ceb576e 191 }
622c9332 192
9ceb576e 193 return $constraint;
194}
195
8ee73eeb 1961;
197
ad46f524 198# ABSTRACT: A union of Moose type constraints
199
8ee73eeb 200__END__
201
202=pod
203
39b3bc94 204=head1 DESCRIPTION
205
ae2f99ea 206This metaclass represents a union of type constraints. A union takes
207multiple type constraints, and is true if any one of its member
208constraints is true.
ecb59493 209
ae2f99ea 210=head1 INHERITANCE
ecb59493 211
ae2f99ea 212C<Moose::Meta::TypeConstraint::Union> is a subclass of
213L<Moose::Meta::TypeConstraint>.
ecb59493 214
39b3bc94 215=over 4
216
ae2f99ea 217=item B<< Moose::Meta::TypeConstraint::Union->new(%options) >>
39b3bc94 218
ae2f99ea 219This creates a new class type constraint based on the given
220C<%options>.
1b58cb9f 221
ae2f99ea 222It takes the same options as its parent. It also requires an
223additional option, C<type_constraints>. This is an array reference
224containing the L<Moose::Meta::TypeConstraint> objects that are the
225members of the union type. The C<name> option defaults to the names
226all of these member types sorted and then joined by a pipe (|).
dabed765 227
ae2f99ea 228The constructor sets the implementation of the constraint so that is
229simply calls C<check> on the newly created object.
ecb59493 230
ae2f99ea 231Finally, the constructor also makes sure that the object's C<coercion>
232attribute is a L<Moose::Meta::TypeCoercion::Union> object.
ecb59493 233
ae2f99ea 234=item B<< $constraint->type_constraints >>
ecb59493 235
ae2f99ea 236This returns the array reference of C<type_constraints> provided to
237the constructor.
39b3bc94 238
c0841d0c 239=item B<< $constraint->parent >>
39b3bc94 240
c0841d0c 241This returns the nearest common ancestor of all the components of the union.
39b3bc94 242
ae2f99ea 243=item B<< $constraint->check($value) >>
39b3bc94 244
ae2f99ea 245=item B<< $constraint->validate($value) >>
39b3bc94 246
ae2f99ea 247These two methods simply call the relevant method on each of the
248member type constraints in the union. If any type accepts the value,
249the value is valid.
39b3bc94 250
ae2f99ea 251With C<validate> the error message returned includes all of the error
252messages returned by the member type constraints.
39b3bc94 253
ae2f99ea 254=item B<< $constraint->equals($type_name_or_object) >>
39b3bc94 255
ae2f99ea 256A type is considered equal if it is also a union type, and the two
257unions have the same member types.
39b3bc94 258
1aae641c 259=item B<< $constraint->find_type_for($value) >>
260
261This returns the first member type constraint for which C<check($value)> is
262true, allowing you to determine which of the Union's member type constraints
263a given value matches.
264
ae2f99ea 265=item B<< $constraint->is_a_type_of($type_name_or_object) >>
ecb59493 266
a93d14d1 267This returns true if all of the member type constraints return true
ae2f99ea 268for the C<is_a_type_of> method.
ecb59493 269
ae2f99ea 270=item B<< $constraint->is_subtype_of >>
39b3bc94 271
a93d14d1 272This returns true if all of the member type constraints return true
ae2f99ea 273for the C<is_a_subtype_of> method.
0eec94be 274
ae2f99ea 275=item B<< $constraint->create_child_type(%options) >>
0eec94be 276
ae2f99ea 277This returns a new L<Moose::Meta::TypeConstraint> object with the type
278as its parent.
9ceb576e 279
39b3bc94 280=back
281
282=head1 BUGS
283
d4048ef3 284See L<Moose/BUGS> for details on reporting bugs.
39b3bc94 285
8ee73eeb 286=cut