File Coverage

blib/lib/Type/Tiny/Union.pm
Criterion Covered Total %
statement 122 123 99.1
branch 42 50 84.0
condition 11 16 68.7
subroutine 30 30 100.0
pod 13 13 100.0
total 218 232 93.9


line stmt bran cond sub pod time code
1             package Type::Tiny::Union;
2              
3 52     52   2759 use 5.008001;
  52         246  
4 52     52   342 use strict;
  52         129  
  52         1209  
5 52     52   262 use warnings;
  52         126  
  52         2598  
6              
7             BEGIN {
8 52     52   232 $Type::Tiny::Union::AUTHORITY = 'cpan:TOBYINK';
9 52         2438 $Type::Tiny::Union::VERSION = '2.003_000';
10             }
11              
12             $Type::Tiny::Union::VERSION =~ tr/_//d;
13              
14 52     52   381 use Scalar::Util qw< blessed >;
  52         131  
  52         3197  
15 52     52   393 use Types::TypeTiny ();
  52         185  
  52         3193  
16              
17 4     4   19 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  4         16  
18              
19 52     52   375 use Type::Tiny ();
  52         115  
  52         124359  
20             our @ISA = 'Type::Tiny';
21              
22             __PACKAGE__->_install_overloads(
23 1220   50 1220   6145 q[@{}] => sub { $_[0]{type_constraints} ||= [] } );
24              
25             sub new_by_overload {
26 109     109 1 247 my $proto = shift;
27 109 50       495 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
28              
29 109         189 my @types = @{ $opts{type_constraints} };
  109         291  
30 109 50 66     897 if ( my @makers = map scalar( blessed($_) && $_->can( 'new_union' ) ), @types ) {
31 109         932 my $first_maker = shift @makers;
32 109 100       1663 if ( ref $first_maker ) {
33 2   33     20 my $all_same = not grep +( !defined $_ or $_ ne $first_maker ), @makers;
34 2 50       7 if ( $all_same ) {
35 2         23 return ref( $types[0] )->$first_maker( %opts );
36             }
37             }
38             }
39              
40 107         379 return $proto->new( \%opts );
41             }
42              
43             sub new {
44 205     205 1 1291 my $proto = shift;
45            
46 205 100       797 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  107         387  
47             _croak
48             "Union type constraints cannot have a parent constraint passed to the constructor"
49 205 100       665 if exists $opts{parent};
50             _croak
51             "Union type constraints cannot have a constraint coderef passed to the constructor"
52 204 100       551 if exists $opts{constraint};
53             _croak
54             "Union type constraints cannot have a inlining coderef passed to the constructor"
55 203 100       467 if exists $opts{inlined};
56             _croak "Need to supply list of type constraints"
57 202 100       597 unless exists $opts{type_constraints};
58            
59             $opts{type_constraints} = [
60 415 100       1281 map { $_->isa( __PACKAGE__ ) ? @$_ : $_ }
61             map Types::TypeTiny::to_TypeTiny( $_ ),
62             @{
63 201         331 ref $opts{type_constraints} eq "ARRAY"
64             ? $opts{type_constraints}
65 201 50       1576 : [ $opts{type_constraints} ]
66             }
67             ];
68            
69 201         416 if ( Type::Tiny::_USE_XS ) {
70 201         373 my @constraints = @{ $opts{type_constraints} };
  201         830  
71             my @known = map {
72 201         439 my $known = Type::Tiny::XS::is_known( $_->compiled_check );
  435         1139  
73 435 100       3319 defined( $known ) ? $known : ();
74             } @constraints;
75            
76 201 100       802 if ( @known == @constraints ) {
77 88         649 my $xsub = Type::Tiny::XS::get_coderef_for(
78             sprintf "AnyOf[%s]",
79             join( ',', @known )
80             );
81 88 50       5091 $opts{compiled_type_constraint} = $xsub if $xsub;
82             }
83             } #/ if ( Type::Tiny::_USE_XS)
84            
85 201         1828 my $self = $proto->SUPER::new( %opts );
86 201 100       732 $self->coercion if grep $_->has_coercion, @$self;
87 201         1761 return $self;
88             } #/ sub new
89              
90             sub _lockdown {
91 201     201   559 my ( $self, $callback ) = @_;
92 201         1731 $callback->( $self->{type_constraints} );
93             }
94              
95 231     231 1 755 sub type_constraints { $_[0]{type_constraints} }
96 86   66 86 1 544 sub constraint { $_[0]{constraint} ||= $_[0]->_build_constraint }
97              
98 180     180   520 sub _is_null_constraint { 0 }
99              
100             sub _build_display_name {
101 117     117   489 my $self = shift;
102 117         541 join q[|], @$self;
103             }
104              
105             sub _build_coercion {
106 74     74   13025 require Type::Coercion::Union;
107 74         201 my $self = shift;
108 74         491 return "Type::Coercion::Union"->new( type_constraint => $self );
109             }
110              
111             sub _build_constraint {
112 52     52   139 my @checks = map $_->compiled_check, @{ +shift };
  52         163  
113             return sub {
114 451     451   829 my $val = $_;
115 451   100     2194 $_->( $val ) && return !!1 for @checks;
116 70         555 return;
117             }
118 52         550 }
119              
120             sub can_be_inlined {
121 354     354 1 5238 my $self = shift;
122 354         949 not grep !$_->can_be_inlined, @$self;
123             }
124              
125             sub inline_check {
126 359     359 1 775 my $self = shift;
127            
128 359 100       1192 if ( Type::Tiny::_USE_XS and !exists $self->{xs_sub} ) {
129 135         438 $self->{xs_sub} = undef;
130            
131 135         245 my @constraints = @{ $self->type_constraints };
  135         404  
132             my @known = map {
133 135         346 my $known = Type::Tiny::XS::is_known( $_->compiled_check );
  291         711  
134 291 100       2148 defined( $known ) ? $known : ();
135             } @constraints;
136            
137 135 100       592 if ( @known == @constraints ) {
138 51         364 $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 359         1415 my $code = sprintf '(%s)', join " or ", map $_->inline_check( $_[0] ), @$self;
146            
147 359 100       3505 return "do { $Type::Tiny::SafePackage $code }"
148             if $Type::Tiny::AvoidCallbacks;
149             return "$self->{xs_sub}\($_[0]\)"
150 316 100       2940 if $self->{xs_sub};
151 230         2080 return $code;
152             } #/ sub inline_check
153              
154             sub _instantiate_moose_type {
155 1     1   2 my $self = shift;
156 1         4 my %opts = @_;
157 1         2 delete $opts{parent};
158 1         2 delete $opts{constraint};
159 1         1 delete $opts{inlined};
160            
161 1         1 my @tc = map $_->moose_type, @{ $self->type_constraints };
  1         3  
162            
163 1         12 require Moose::Meta::TypeConstraint::Union;
164 1         11 return "Moose::Meta::TypeConstraint::Union"
165             ->new( %opts, type_constraints => \@tc );
166             } #/ sub _instantiate_moose_type
167              
168             sub has_parent {
169 113     113 1 10968 defined( shift->parent );
170             }
171              
172             sub parent {
173 267   100 267 1 1289 $_[0]{parent} ||= $_[0]->_build_parent;
174             }
175              
176             sub _build_parent {
177 74     74   166 my $self = shift;
178 74         216 my ( $first, @rest ) = @$self;
179            
180 74         376 for my $parent ( $first, $first->parents ) {
181 218 100       892 return $parent unless grep !$_->is_a_type_of( $parent ), @rest;
182             }
183            
184 9         59 return;
185             } #/ sub _build_parent
186              
187             sub find_type_for {
188 4     4 1 8 my @types = @{ +shift };
  4         15  
189 4         13 for my $type ( @types ) {
190 7 100       26 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         3 my ( $value, $varname ) = @_;
198 1 50       4 $varname = '$_' unless defined $varname;
199            
200 1 50       8 return undef if $self->check( $value );
201            
202 1         529 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       7 map( " $_", @{ $_->validate_explain( $value ) || [] } ),
  2         8  
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         8 $self->$_delegate( stringifies_to => @_ );
230             }
231              
232             sub numifies_to {
233 2     2 1 15 my $self = shift;
234 2         7 $self->$_delegate( numifies_to => @_ );
235             }
236              
237             sub with_attribute_values {
238 1     1 1 4 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__
363              
364             =pod
365              
366             =encoding utf-8
367              
368             =head1 NAME
369              
370             Type::Tiny::Union - union type constraints
371              
372             =head1 SYNOPSIS
373              
374             Using via the C<< | >> operator overload:
375              
376             package Local::Stash {
377             use Moo;
378             use Types::Common qw( ArrayRef HashRef );
379            
380             has data => (
381             is => 'ro',
382             isa => HashRef | ArrayRef,
383             );
384             }
385            
386             my $x = Local::Stash->new( data => {} ); # ok
387             my $y = Local::Stash->new( data => [] ); # ok
388              
389             Using Type::Tiny::Union's object-oriented interface:
390              
391             package Local::Stash {
392             use Moo;
393             use Types::Common qw( ArrayRef HashRef );
394             use Type::Tiny::Union;
395            
396             my $AnyData = Type::Tiny::Union->new(
397             name => 'AnyData',
398             type_constraints => [ HashRef, ArrayRef ],
399             );
400            
401             has data => (
402             is => 'ro',
403             isa => $AnyData,
404             );
405             }
406              
407             Using Type::Utils's functional interface:
408              
409             package Local::Stash {
410             use Moo;
411             use Types::Common qw( ArrayRef HashRef );
412             use Type::Utils;
413            
414             my $AnyData = union AnyData => [ HashRef, ArrayRef ];
415            
416             has data => (
417             is => 'ro',
418             isa => $AnyData,
419             );
420             }
421              
422             =head1 STATUS
423              
424             This module is covered by the
425             L<Type-Tiny stability policy|Type::Tiny::Manual::Policies/"STABILITY">.
426              
427             =head1 DESCRIPTION
428              
429             Union type constraints.
430              
431             This package inherits from L<Type::Tiny>; see that for most documentation.
432             Major differences are listed below:
433              
434             =head2 Constructor
435              
436             The C<new> constructor from L<Type::Tiny> still works, of course. But there
437             is also:
438              
439             =over
440              
441             =item C<< new_by_overload(%attributes) >>
442              
443             Like the C<new> constructor, but will sometimes return another type
444             constraint which is not strictly an instance of L<Type::Tiny::Union>, but
445             still encapsulates the same meaning. This constructor is used by
446             Type::Tiny's overloading of the C<< | >> operator.
447              
448             =back
449              
450             =head2 Attributes
451              
452             =over
453              
454             =item C<type_constraints>
455              
456             Arrayref of type constraints.
457              
458             When passed to the constructor, if any of the type constraints in the union
459             is itself a union type constraint, this is "exploded" into the new union.
460              
461             =item C<constraint>
462              
463             Unlike Type::Tiny, you I<cannot> pass a constraint coderef to the constructor.
464             Instead rely on the default.
465              
466             =item C<inlined>
467              
468             Unlike Type::Tiny, you I<cannot> pass an inlining coderef to the constructor.
469             Instead rely on the default.
470              
471             =item C<parent>
472              
473             Unlike Type::Tiny, you I<cannot> pass an inlining coderef to the constructor.
474             A parent will instead be automatically calculated.
475              
476             =item C<coercion>
477              
478             You probably do not pass this to the constructor. (It's not currently
479             disallowed, as there may be a use for it that I haven't thought of.)
480              
481             The auto-generated default will be a L<Type::Coercion::Union> object.
482              
483             =back
484              
485             =head2 Methods
486              
487             =over
488              
489             =item C<< find_type_for($value) >>
490              
491             Returns the first individual type constraint in the union which
492             C<< $value >> passes.
493              
494             =item C<< stringifies_to($constraint) >>
495              
496             See L<Type::Tiny::ConstrainedObject>.
497              
498             =item C<< numifies_to($constraint) >>
499              
500             See L<Type::Tiny::ConstrainedObject>.
501              
502             =item C<< with_attribute_values($attr1 => $constraint1, ...) >>
503              
504             See L<Type::Tiny::ConstrainedObject>.
505              
506             =back
507              
508             =head2 Overloading
509              
510             =over
511              
512             =item *
513              
514             Arrayrefification calls C<type_constraints>.
515              
516             =back
517              
518             =head1 BUGS
519              
520             Please report any bugs to
521             L<https://github.com/tobyink/p5-type-tiny/issues>.
522              
523             =head1 SEE ALSO
524              
525             L<Type::Tiny::Manual>.
526              
527             L<Type::Tiny>.
528              
529             =head1 AUTHOR
530              
531             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
532              
533             =head1 COPYRIGHT AND LICENCE
534              
535             This software is copyright (c) 2013-2014, 2017-2023 by Toby Inkster.
536              
537             This is free software; you can redistribute it and/or modify it under
538             the same terms as the Perl 5 programming language system itself.
539              
540             =head1 DISCLAIMER OF WARRANTIES
541              
542             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
543             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
544             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.