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   2561 use 5.008001;
  45         163  
4 45     45   487 use strict;
  45         113  
  45         1283  
5 45     45   237 use warnings;
  45         106  
  45         2228  
6              
7             BEGIN {
8 45     45   358 $Type::Tiny::Union::AUTHORITY = 'cpan:TOBYINK';
9 45         2208 $Type::Tiny::Union::VERSION = '2.002001';
10             }
11              
12             $Type::Tiny::Union::VERSION =~ tr/_//d;
13              
14 45     45   326 use Scalar::Util qw< blessed >;
  45         120  
  45         3007  
15 45     45   367 use Types::TypeTiny ();
  45         187  
  45         2973  
16              
17 4     4   393 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  4         15  
18              
19 45     45   322 use Type::Tiny ();
  45         146  
  45         109871  
20             our @ISA = 'Type::Tiny';
21              
22             __PACKAGE__->_install_overloads(
23 828   50 828   3871 q[@{}] => sub { $_[0]{type_constraints} ||= [] } );
24              
25             sub new_by_overload {
26 82     82 1 145 my $proto = shift;
27 82 50       323 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
28              
29 82         124 my @types = @{ $opts{type_constraints} };
  82         195  
30 82 50 66     601 if ( my @makers = map scalar( blessed($_) && $_->can( 'new_union' ) ), @types ) {
31 82         146 my $first_maker = shift @makers;
32 82 100       200 if ( ref $first_maker ) {
33 1   33     7 my $all_same = not grep +( !defined $_ or $_ ne $first_maker ), @makers;
34 1 50       3 if ( $all_same ) {
35 1         7 return ref( $types[0] )->$first_maker( %opts );
36             }
37             }
38             }
39              
40 81         231 return $proto->new( \%opts );
41             }
42              
43             sub new {
44 165     165 1 348 my $proto = shift;
45            
46 165 100       586 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  81         273  
47             _croak
48             "Union type constraints cannot have a parent constraint passed to the constructor"
49 165 100       483 if exists $opts{parent};
50             _croak
51             "Union type constraints cannot have a constraint coderef passed to the constructor"
52 164 100       409 if exists $opts{constraint};
53             _croak
54             "Union type constraints cannot have a inlining coderef passed to the constructor"
55 163 100       360 if exists $opts{inlined};
56             _croak "Need to supply list of type constraints"
57 162 100       414 unless exists $opts{type_constraints};
58            
59             $opts{type_constraints} = [
60 334 100       898 map { $_->isa( __PACKAGE__ ) ? @$_ : $_ }
61             map Types::TypeTiny::to_TypeTiny( $_ ),
62             @{
63 161         247 ref $opts{type_constraints} eq "ARRAY"
64             ? $opts{type_constraints}
65 161 50       870 : [ $opts{type_constraints} ]
66             }
67             ];
68            
69 161         319 if ( Type::Tiny::_USE_XS ) {
70 161         262 my @constraints = @{ $opts{type_constraints} };
  161         408  
71             my @known = map {
72 161         301 my $known = Type::Tiny::XS::is_known( $_->compiled_check );
  353         827  
73 353 100       2504 defined( $known ) ? $known : ();
74             } @constraints;
75            
76 161 100       512 if ( @known == @constraints ) {
77 58         342 my $xsub = Type::Tiny::XS::get_coderef_for(
78             sprintf "AnyOf[%s]",
79             join( ',', @known )
80             );
81 58 50       2791 $opts{compiled_type_constraint} = $xsub if $xsub;
82             }
83             } #/ if ( Type::Tiny::_USE_XS)
84            
85 161         912 my $self = $proto->SUPER::new( %opts );
86 161 100       526 $self->coercion if grep $_->has_coercion, @$self;
87 161         1307 return $self;
88             } #/ sub new
89              
90             sub _lockdown {
91 161     161   383 my ( $self, $callback ) = @_;
92 161         1344 $callback->( $self->{type_constraints} );
93             }
94              
95 187     187 1 630 sub type_constraints { $_[0]{type_constraints} }
96 53   66 53 1 273 sub constraint { $_[0]{constraint} ||= $_[0]->_build_constraint }
97              
98 166     166   481 sub _is_null_constraint { 0 }
99              
100             sub _build_display_name {
101 89     89   190 my $self = shift;
102 89         236 join q[|], @$self;
103             }
104              
105             sub _build_coercion {
106 65     65   10952 require Type::Coercion::Union;
107 65         164 my $self = shift;
108 65         415 return "Type::Coercion::Union"->new( type_constraint => $self );
109             }
110              
111             sub _build_constraint {
112 24     24   62 my @checks = map $_->compiled_check, @{ +shift };
  24         81  
113             return sub {
114 182     182   283 my $val = $_;
115 182   100     549 $_->( $val ) && return !!1 for @checks;
116 12         117 return;
117             }
118 24         217 }
119              
120             sub can_be_inlined {
121 235     235 1 427 my $self = shift;
122 235         561 not grep !$_->can_be_inlined, @$self;
123             }
124              
125             sub inline_check {
126 230     230 1 531 my $self = shift;
127            
128 230 100       689 if ( Type::Tiny::_USE_XS and !exists $self->{xs_sub} ) {
129 101         274 $self->{xs_sub} = undef;
130            
131 101         181 my @constraints = @{ $self->type_constraints };
  101         636  
132             my @known = map {
133 101         233 my $known = Type::Tiny::XS::is_known( $_->compiled_check );
  221         1146  
134 221 100       1580 defined( $known ) ? $known : ();
135             } @constraints;
136            
137 101 100       370 if ( @known == @constraints ) {
138 25         143 $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 230         783 my $code = sprintf '(%s)', join " or ", map $_->inline_check( $_[0] ), @$self;
146            
147 230 100       2853 return "do { $Type::Tiny::SafePackage $code }"
148             if $Type::Tiny::AvoidCallbacks;
149             return "$self->{xs_sub}\($_[0]\)"
150 196 100       1866 if $self->{xs_sub};
151 160         1901 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 77     77 1 190 defined( shift->parent );
170             }
171              
172             sub parent {
173 196   100 196 1 809 $_[0]{parent} ||= $_[0]->_build_parent;
174             }
175              
176             sub _build_parent {
177 40     40   85 my $self = shift;
178 40         110 my ( $first, @rest ) = @$self;
179            
180 40         181 for my $parent ( $first, $first->parents ) {
181 137 100       514 return $parent unless grep !$_->is_a_type_of( $parent ), @rest;
182             }
183            
184 5         22 return;
185             } #/ sub _build_parent
186              
187             sub find_type_for {
188 4     4 1 7 my @types = @{ +shift };
  4         11  
189 4         9 for my $type ( @types ) {
190 7 100       19 return $type if $type->check( @_ );
191             }
192 1         5 return;
193             }
194              
195             sub validate_explain {
196 1     1 1 4 my $self = shift;
197 1         2 my ( $value, $varname ) = @_;
198 1 50       40 $varname = '$_' unless defined $varname;
199            
200 1 50       9 return undef if $self->check( $value );
201            
202 1         527 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         6 $_->get_message( $value ),
211 2 50       7 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 14 my $self = shift;
229 2         4 $self->$_delegate( stringifies_to => @_ );
230             }
231              
232             sub numifies_to {
233 2     2 1 13 my $self = shift;
234 2         8 $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__