File Coverage

blib/lib/Type/Tiny/Union.pm
Criterion Covered Total %
statement 113 123 91.8
branch 42 50 84.0
condition 11 16 68.7
subroutine 29 30 96.6
pod 13 13 100.0
total 208 232 89.6


line stmt bran cond sub pod time code
1             package Type::Tiny::Union;
2              
3 45     45   2630 use 5.008001;
  45         170  
4 45     45   298 use strict;
  45         97  
  45         1122  
5 45     45   238 use warnings;
  45         91  
  45         2234  
6              
7             BEGIN {
8 45     45   194 $Type::Tiny::Union::AUTHORITY = 'cpan:TOBYINK';
9 45         1902 $Type::Tiny::Union::VERSION = '2.004000';
10             }
11              
12             $Type::Tiny::Union::VERSION =~ tr/_//d;
13              
14 45     45   299 use Scalar::Util qw< blessed >;
  45         118  
  45         3021  
15 45     45   367 use Types::TypeTiny ();
  45         105  
  45         2803  
16              
17 4     4   25 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  4         16  
18              
19 45     45   314 use Type::Tiny ();
  45         109  
  45         104699  
20             our @ISA = 'Type::Tiny';
21              
22             __PACKAGE__->_install_overloads(
23 873   50 873   4232 q[@{}] => sub { $_[0]{type_constraints} ||= [] } );
24              
25             sub new_by_overload {
26 82     82 1 144 my $proto = shift;
27 82 50       307 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
28              
29 82         124 my @types = @{ $opts{type_constraints} };
  82         176  
30 82 50 66     562 if ( my @makers = map scalar( blessed($_) && $_->can( 'new_union' ) ), @types ) {
31 82         140 my $first_maker = shift @makers;
32 82 100       197 if ( ref $first_maker ) {
33 1   33     7 my $all_same = not grep +( !defined $_ or $_ ne $first_maker ), @makers;
34 1 50       4 if ( $all_same ) {
35 1         6 return ref( $types[0] )->$first_maker( %opts );
36             }
37             }
38             }
39              
40 81         274 return $proto->new( \%opts );
41             }
42              
43             sub new {
44 165     165 1 348 my $proto = shift;
45            
46 165 100       565 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  81         264  
47             _croak
48             "Union type constraints cannot have a parent constraint passed to the constructor"
49 165 100       544 if exists $opts{parent};
50             _croak
51             "Union type constraints cannot have a constraint coderef passed to the constructor"
52 164 100       394 if exists $opts{constraint};
53             _croak
54             "Union type constraints cannot have a inlining coderef passed to the constructor"
55 163 100       372 if exists $opts{inlined};
56             _croak "Need to supply list of type constraints"
57 162 100       389 unless exists $opts{type_constraints};
58            
59             $opts{type_constraints} = [
60 334 100       917 map { $_->isa( __PACKAGE__ ) ? @$_ : $_ }
61             map Types::TypeTiny::to_TypeTiny( $_ ),
62             @{
63 161         243 ref $opts{type_constraints} eq "ARRAY"
64             ? $opts{type_constraints}
65 161 50       866 : [ $opts{type_constraints} ]
66             }
67             ];
68            
69 161         388 if ( Type::Tiny::_USE_XS ) {
70 161         239 my @constraints = @{ $opts{type_constraints} };
  161         382  
71             my @known = map {
72 161         352 my $known = Type::Tiny::XS::is_known( $_->compiled_check );
  353         829  
73 353 100       2487 defined( $known ) ? $known : ();
74             } @constraints;
75            
76 161 100       515 if ( @known == @constraints ) {
77 58         357 my $xsub = Type::Tiny::XS::get_coderef_for(
78             sprintf "AnyOf[%s]",
79             join( ',', @known )
80             );
81 58 50       2961 $opts{compiled_type_constraint} = $xsub if $xsub;
82             }
83             } #/ if ( Type::Tiny::_USE_XS)
84            
85 161         974 my $self = $proto->SUPER::new( %opts );
86 161 100       528 $self->coercion if grep $_->has_coercion, @$self;
87 161         1262 return $self;
88             } #/ sub new
89              
90             sub _lockdown {
91 161     161   385 my ( $self, $callback ) = @_;
92 161         1455 $callback->( $self->{type_constraints} );
93             }
94              
95 187     187 1 643 sub type_constraints { $_[0]{type_constraints} }
96 53   66 53 1 278 sub constraint { $_[0]{constraint} ||= $_[0]->_build_constraint }
97              
98 166     166   505 sub _is_null_constraint { 0 }
99              
100             sub _build_display_name {
101 89     89   179 my $self = shift;
102 89         232 join q[|], @$self;
103             }
104              
105             sub _build_coercion {
106 65     65   11078 require Type::Coercion::Union;
107 65         196 my $self = shift;
108 65         459 return "Type::Coercion::Union"->new( type_constraint => $self );
109             }
110              
111             sub _build_constraint {
112 24     24   57 my @checks = map $_->compiled_check, @{ +shift };
  24         69  
113             return sub {
114 182     182   287 my $val = $_;
115 182   100     555 $_->( $val ) && return !!1 for @checks;
116 12         123 return;
117             }
118 24         216 }
119              
120             sub can_be_inlined {
121 235     235 1 450 my $self = shift;
122 235         595 not grep !$_->can_be_inlined, @$self;
123             }
124              
125             sub inline_check {
126 272     272 1 518 my $self = shift;
127            
128 272 100       732 if ( Type::Tiny::_USE_XS and !exists $self->{xs_sub} ) {
129 101         286 $self->{xs_sub} = undef;
130            
131 101         176 my @constraints = @{ $self->type_constraints };
  101         255  
132             my @known = map {
133 101         266 my $known = Type::Tiny::XS::is_known( $_->compiled_check );
  221         587  
134 221 100       1542 defined( $known ) ? $known : ();
135             } @constraints;
136            
137 101 100       377 if ( @known == @constraints ) {
138 25         166 $self->{xs_sub} = Type::Tiny::XS::get_subname_for(
139             sprintf "AnyOf[%s]",
140             join( ',', @known )
141             );
142             }
143             } #/ if ( Type::Tiny::_USE_XS...)
144            
145 272         890 my $code = sprintf '(%s)', join " or ", map $_->inline_check( $_[0] ), @$self;
146            
147 272 100       2855 return "do { $Type::Tiny::SafePackage $code }"
148             if $Type::Tiny::AvoidCallbacks;
149             return "$self->{xs_sub}\($_[0]\)"
150 238 100       2030 if $self->{xs_sub};
151 202         1796 return $code;
152             } #/ sub inline_check
153              
154             sub _instantiate_moose_type {
155 0     0   0 my $self = shift;
156 0         0 my %opts = @_;
157 0         0 delete $opts{parent};
158 0         0 delete $opts{constraint};
159 0         0 delete $opts{inlined};
160            
161 0         0 my @tc = map $_->moose_type, @{ $self->type_constraints };
  0         0  
162            
163 0         0 require Moose::Meta::TypeConstraint::Union;
164 0         0 return "Moose::Meta::TypeConstraint::Union"
165             ->new( %opts, type_constraints => \@tc );
166             } #/ sub _instantiate_moose_type
167              
168             sub has_parent {
169 80     80 1 204 defined( shift->parent );
170             }
171              
172             sub parent {
173 202   100 202 1 893 $_[0]{parent} ||= $_[0]->_build_parent;
174             }
175              
176             sub _build_parent {
177 43     43   90 my $self = shift;
178 43         107 my ( $first, @rest ) = @$self;
179            
180 43         203 for my $parent ( $first, $first->parents ) {
181 149 100       529 return $parent unless grep !$_->is_a_type_of( $parent ), @rest;
182             }
183            
184 5         29 return;
185             } #/ sub _build_parent
186              
187             sub find_type_for {
188 4     4 1 10 my @types = @{ +shift };
  4         14  
189 4         11 for my $type ( @types ) {
190 7 100       35 return $type if $type->check( @_ );
191             }
192 1         5 return;
193             }
194              
195             sub validate_explain {
196 1     1 1 3 my $self = shift;
197 1         3 my ( $value, $varname ) = @_;
198 1 50       2 $varname = '$_' unless defined $varname;
199            
200 1 50       19 return undef if $self->check( $value );
201            
202 1         726 require Type::Utils;
203             return [
204             sprintf(
205             '"%s" requires that the value pass %s',
206             $self,
207             Type::Utils::english_list( \"or", map qq["$_"], @$self ),
208             ),
209             map {
210 1         11 $_->get_message( $value ),
211 2 50       11 map( " $_", @{ $_->validate_explain( $value ) || [] } ),
  2         9  
212             } @$self
213             ];
214             } #/ sub validate_explain
215              
216             my $_delegate = sub {
217             my ( $self, $method ) = ( shift, shift );
218             my @types = @{ $self->type_constraints };
219            
220             my @unsupported = grep !$_->can( $method ), @types;
221             _croak( 'Could not apply method %s to all types within the union', $method )
222             if @unsupported;
223            
224             ref( $self )->new( type_constraints => [ map $_->$method( @_ ), @types ] );
225             };
226              
227             sub stringifies_to {
228 2     2 1 18 my $self = shift;
229 2         8 $self->$_delegate( stringifies_to => @_ );
230             }
231              
232             sub numifies_to {
233 2     2 1 14 my $self = shift;
234 2         6 $self->$_delegate( numifies_to => @_ );
235             }
236              
237             sub with_attribute_values {
238 1     1 1 2 my $self = shift;
239 1         3 $self->$_delegate( with_attribute_values => @_ );
240             }
241              
242             push @Type::Tiny::CMP, sub {
243             my $A = shift->find_constraining_type;
244             my $B = shift->find_constraining_type;
245            
246             if ( $A->isa( __PACKAGE__ ) and $B->isa( __PACKAGE__ ) ) {
247             my @A_constraints = @{ $A->type_constraints };
248             my @B_constraints = @{ $B->type_constraints };
249            
250             # If everything in @A_constraints is equal to something in @B_constraints and vice versa, then $A equiv to $B
251             EQUALITY: {
252             my $everything_in_a_is_equal = 1;
253             OUTER: for my $A_child ( @A_constraints ) {
254             INNER: for my $B_child ( @B_constraints ) {
255             if ( $A_child->equals( $B_child ) ) {
256             next OUTER;
257             }
258             }
259             $everything_in_a_is_equal = 0;
260             last OUTER;
261             }
262            
263             my $everything_in_b_is_equal = 1;
264             OUTER: for my $B_child ( @B_constraints ) {
265             INNER: for my $A_child ( @A_constraints ) {
266             if ( $B_child->equals( $A_child ) ) {
267             next OUTER;
268             }
269             }
270             $everything_in_b_is_equal = 0;
271             last OUTER;
272             }
273            
274             return Type::Tiny::CMP_EQUIVALENT
275             if $everything_in_a_is_equal && $everything_in_b_is_equal;
276             } #/ EQUALITY:
277            
278             # If everything in @A_constraints is a subtype of something in @B_constraints, then $A is subtype of $B
279             SUBTYPE: {
280             OUTER: for my $A_child ( @A_constraints ) {
281             my $a_child_is_subtype_of_something = 0;
282             INNER: for my $B_child ( @B_constraints ) {
283             if ( $A_child->is_a_type_of( $B_child ) ) {
284             ++$a_child_is_subtype_of_something;
285             last INNER;
286             }
287             }
288             if ( not $a_child_is_subtype_of_something ) {
289             last SUBTYPE;
290             }
291             } #/ OUTER: for my $A_child ( @A_constraints)
292             return Type::Tiny::CMP_SUBTYPE;
293             } #/ SUBTYPE:
294            
295             # If everything in @B_constraints is a subtype of something in @A_constraints, then $A is supertype of $B
296             SUPERTYPE: {
297             OUTER: for my $B_child ( @B_constraints ) {
298             my $b_child_is_subtype_of_something = 0;
299             INNER: for my $A_child ( @A_constraints ) {
300             if ( $B_child->is_a_type_of( $A_child ) ) {
301             ++$b_child_is_subtype_of_something;
302             last INNER;
303             }
304             }
305             if ( not $b_child_is_subtype_of_something ) {
306             last SUPERTYPE;
307             }
308             } #/ OUTER: for my $B_child ( @B_constraints)
309             return Type::Tiny::CMP_SUPERTYPE;
310             } #/ SUPERTYPE:
311             } #/ if ( $A->isa( __PACKAGE__...))
312            
313             # I think it might be possible to merge this into the first bit by treating $B as union[$B].
314             # Test cases first though.
315             if ( $A->isa( __PACKAGE__ ) ) {
316             my @A_constraints = @{ $A->type_constraints };
317             if ( @A_constraints == 1 ) {
318             my $result = Type::Tiny::cmp( $A_constraints[0], $B );
319             return $result unless $result eq Type::Tiny::CMP_UNKNOWN;
320             }
321             my $subtype = 1;
322             for my $child ( @A_constraints ) {
323             if ( $B->is_a_type_of( $child ) ) {
324             return Type::Tiny::CMP_SUPERTYPE;
325             }
326             if ( $subtype and not $B->is_supertype_of( $child ) ) {
327             $subtype = 0;
328             }
329             }
330             if ( $subtype ) {
331             return Type::Tiny::CMP_SUBTYPE;
332             }
333             } #/ if ( $A->isa( __PACKAGE__...))
334            
335             # I think it might be possible to merge this into the first bit by treating $A as union[$A].
336             # Test cases first though.
337             if ( $B->isa( __PACKAGE__ ) ) {
338             my @B_constraints = @{ $B->type_constraints };
339             if ( @B_constraints == 1 ) {
340             my $result = Type::Tiny::cmp( $A, $B_constraints[0] );
341             return $result unless $result eq Type::Tiny::CMP_UNKNOWN;
342             }
343             my $supertype = 1;
344             for my $child ( @B_constraints ) {
345             if ( $A->is_a_type_of( $child ) ) {
346             return Type::Tiny::CMP_SUBTYPE;
347             }
348             if ( $supertype and not $A->is_supertype_of( $child ) ) {
349             $supertype = 0;
350             }
351             }
352             if ( $supertype ) {
353             return Type::Tiny::CMP_SUPERTYPE;
354             }
355             } #/ if ( $B->isa( __PACKAGE__...))
356            
357             return Type::Tiny::CMP_UNKNOWN;
358             };
359              
360             1;
361              
362             __END__