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   3144 use 5.008001;
  45         214  
4 45     45   298 use strict;
  45         83  
  45         1260  
5 45     45   315 use warnings;
  45         124  
  45         4106  
6              
7             BEGIN {
8 45     45   170 $Type::Tiny::Union::AUTHORITY = 'cpan:TOBYINK';
9 45         2537 $Type::Tiny::Union::VERSION = '2.010001';
10             }
11              
12             $Type::Tiny::Union::VERSION =~ tr/_//d;
13              
14 45     45   278 use Scalar::Util qw< blessed >;
  45         150  
  45         3480  
15 45     45   290 use Types::TypeTiny ();
  45         88  
  45         3078  
16              
17 4     4   65 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  4         25  
18              
19 45     45   256 use Type::Tiny ();
  45         218  
  45         118035  
20             our @ISA = 'Type::Tiny';
21              
22             __PACKAGE__->_install_overloads(
23 922   50 922   11283 q[@{}] => sub { $_[0]{type_constraints} ||= [] } );
24              
25             sub new_by_overload {
26 83     83 1 164 my $proto = shift;
27 83 50       365 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
28              
29 83         151 my @types = @{ $opts{type_constraints} };
  83         236  
30 83 50 66     560 if ( my @makers = map scalar( blessed($_) && $_->can( 'new_union' ) ), @types ) {
31 83         170 my $first_maker = shift @makers;
32 83 100       248 if ( ref $first_maker ) {
33 1   33     7 my $all_same = not grep +( !defined $_ or $_ ne $first_maker ), @makers;
34 1 50       2 if ( $all_same ) {
35 1         7 return ref( $types[0] )->$first_maker( %opts );
36             }
37             }
38             }
39              
40 82         297 return $proto->new( \%opts );
41             }
42              
43             sub new {
44 170     170 1 410 my $proto = shift;
45            
46 170 100       788 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  82         282  
47             _croak
48             "Union type constraints cannot have a parent constraint passed to the constructor"
49 170 100       553 if exists $opts{parent};
50             _croak
51             "Union type constraints cannot have a constraint coderef passed to the constructor"
52 169 100       485 if exists $opts{constraint};
53             _croak
54             "Union type constraints cannot have a inlining coderef passed to the constructor"
55 168 100       461 if exists $opts{inlined};
56             _croak "Need to supply list of type constraints"
57 167 100       571 unless exists $opts{type_constraints};
58            
59             $opts{type_constraints} = [
60 343 100       992 map { $_->isa( __PACKAGE__ ) ? @$_ : $_ }
61             map Types::TypeTiny::to_TypeTiny( $_ ),
62             @{
63 166         288 ref $opts{type_constraints} eq "ARRAY"
64             ? $opts{type_constraints}
65 166 50       1210 : [ $opts{type_constraints} ]
66             }
67             ];
68            
69 166         315 if ( Type::Tiny::_USE_XS ) {
70 166         297 my @constraints = @{ $opts{type_constraints} };
  166         413  
71             my @known = map {
72 166         363 my $known = Type::Tiny::XS::is_known( $_->compiled_check );
  362         933  
73 362 100       3185 defined( $known ) ? $known : ();
74             } @constraints;
75            
76 166 100       581 if ( @known == @constraints ) {
77 63         445 my $xsub = Type::Tiny::XS::get_coderef_for(
78             sprintf "AnyOf[%s]",
79             join( ',', @known )
80             );
81 63 50       3406 $opts{compiled_type_constraint} = $xsub if $xsub;
82             }
83             } #/ if ( Type::Tiny::_USE_XS)
84            
85 166         1594 my $self = $proto->SUPER::new( %opts );
86 166 100       630 $self->coercion if grep $_->has_coercion, @$self;
87 166         1631 return $self;
88             } #/ sub new
89              
90             sub _lockdown {
91 166     166   430 my ( $self, $callback ) = @_;
92 166         1548 $callback->( $self->{type_constraints} );
93             }
94              
95 191     191 1 682 sub type_constraints { $_[0]{type_constraints} }
96 53   66 53 1 360 sub constraint { $_[0]{constraint} ||= $_[0]->_build_constraint }
97              
98 169     169   528 sub _is_null_constraint { 0 }
99              
100             sub _build_display_name {
101 94     94   188 my $self = shift;
102 94         303 join q[|], @$self;
103             }
104              
105             sub _build_coercion {
106 65     65   13404 require Type::Coercion::Union;
107 65         198 my $self = shift;
108 65         2422 return "Type::Coercion::Union"->new( type_constraint => $self );
109             }
110              
111             sub _build_constraint {
112 24     24   77 my @checks = map $_->compiled_check, @{ +shift };
  24         80  
113             return sub {
114 182     182   350 my $val = $_;
115 182   100     657 $_->( $val ) && return !!1 for @checks;
116 12         242 return;
117             }
118 24         186 }
119              
120             sub can_be_inlined {
121 246     246 1 488 my $self = shift;
122 246         697 not grep !$_->can_be_inlined, @$self;
123             }
124              
125             sub inline_check {
126 295     295 1 596 my $self = shift;
127            
128 295 100       928 if ( Type::Tiny::_USE_XS and !exists $self->{xs_sub} ) {
129 104         308 $self->{xs_sub} = undef;
130            
131 104         181 my @constraints = @{ $self->type_constraints };
  104         294  
132             my @known = map {
133 104         235 my $known = Type::Tiny::XS::is_known( $_->compiled_check );
  227         901  
134 227 100       1613 defined( $known ) ? $known : ();
135             } @constraints;
136            
137 104 100       493 if ( @known == @constraints ) {
138 28         190 $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 295         1225 my $code = sprintf '(%s)', join " or ", map $_->inline_check( $_[0] ), @$self;
146            
147 295 100       3110 return "do { $Type::Tiny::SafePackage $code }"
148             if $Type::Tiny::AvoidCallbacks;
149             return "$self->{xs_sub}\($_[0]\)"
150 255 100       2349 if $self->{xs_sub};
151 202         1920 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 99     99 1 308 defined( shift->parent );
170             }
171              
172             sub parent {
173 259   100 259 1 1222 $_[0]{parent} ||= $_[0]->_build_parent;
174             }
175              
176             sub _build_parent {
177 48     48   118 my $self = shift;
178 48         143 my ( $first, @rest ) = @$self;
179            
180 48         214 for my $parent ( $first, $first->parents ) {
181 160 100       623 return $parent unless grep !$_->is_a_type_of( $parent ), @rest;
182             }
183            
184 5         20 return;
185             } #/ sub _build_parent
186              
187             sub find_type_for {
188 4     4 1 11 my @types = @{ +shift };
  4         16  
189 4         11 for my $type ( @types ) {
190 7 100       32 return $type if $type->check( @_ );
191             }
192 1         7 return;
193             }
194              
195             sub validate_explain {
196 1     1 1 1 my $self = shift;
197 1         2 my ( $value, $varname ) = @_;
198 1 50       3 $varname = '$_' unless defined $varname;
199            
200 1 50       6 return undef if $self->check( $value );
201            
202 1         481 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         9 $_->get_message( $value ),
211 2 50       5 map( " $_", @{ $_->validate_explain( $value ) || [] } ),
  2         5  
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 22 my $self = shift;
229 2         8 $self->$_delegate( stringifies_to => @_ );
230             }
231              
232             sub numifies_to {
233 2     2 1 16 my $self = shift;
234 2         7 $self->$_delegate( numifies_to => @_ );
235             }
236              
237             sub with_attribute_values {
238 1     1 1 3 my $self = shift;
239 1         4 $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__