File Coverage

blib/lib/Type/Tiny/Intersection.pm
Criterion Covered Total %
statement 99 101 99.0
branch 36 44 81.8
condition 8 10 80.0
subroutine 26 26 100.0
pod 12 12 100.0
total 181 193 94.3


line stmt bran cond sub pod time code
1             package Type::Tiny::Intersection;
2              
3 20     20   1997 use 5.008001;
  20         81  
4 20     20   130 use strict;
  20         53  
  20         479  
5 20     20   127 use warnings;
  20         53  
  20         934  
6              
7             BEGIN {
8 20     20   87 $Type::Tiny::Intersection::AUTHORITY = 'cpan:TOBYINK';
9 20         829 $Type::Tiny::Intersection::VERSION = '2.003_000';
10             }
11              
12             $Type::Tiny::Intersection::VERSION =~ tr/_//d;
13              
14 20     20   139 use Scalar::Util qw< blessed >;
  20         80  
  20         1127  
15 20     20   133 use Types::TypeTiny ();
  20         66  
  20         1232  
16              
17 4     4   22 sub _croak ($;@) { require Error::TypeTiny; goto \&Error::TypeTiny::croak }
  4         16  
18              
19 20     20   132 use Type::Tiny ();
  20         46  
  20         34166  
20             our @ISA = 'Type::Tiny';
21              
22             __PACKAGE__->_install_overloads(
23 343   50 343   1697 q[@{}] => sub { $_[0]{type_constraints} ||= [] },
24             );
25              
26             sub new_by_overload {
27 40067     40067 1 79978 my $proto = shift;
28 40067 50       130157 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  0         0  
29              
30 40067         82585 my @types = @{ $opts{type_constraints} };
  40067         91917  
31 40067 50 100     236134 if ( my @makers = map scalar( blessed($_) && $_->can( 'new_intersection' ) ), @types ) {
32 40067         77485 my $first_maker = shift @makers;
33 40067 100       92515 if ( ref $first_maker ) {
34 1         5 my $all_same = not grep $_ ne $first_maker, @makers;
35 1 50       3 if ( $all_same ) {
36 1         6 return ref( $types[0] )->$first_maker( %opts );
37             }
38             }
39             }
40              
41 40066         113237 return $proto->new( \%opts );
42             }
43              
44             sub new {
45 40091     40091 1 68432 my $proto = shift;
46            
47 40091 100       87878 my %opts = ( @_ == 1 ) ? %{ $_[0] } : @_;
  40066         131611  
48             _croak "Intersection type constraints cannot have a parent constraint"
49 40091 100       101039 if exists $opts{parent};
50             _croak
51             "Intersection type constraints cannot have a constraint coderef passed to the constructor"
52 40090 100       87119 if exists $opts{constraint};
53             _croak
54             "Intersection type constraints cannot have a inlining coderef passed to the constructor"
55 40089 100       82428 if exists $opts{inlined};
56             _croak "Need to supply list of type constraints"
57 40088 100       93998 unless exists $opts{type_constraints};
58            
59             $opts{type_constraints} = [
60 80185 100       190743 map { $_->isa( __PACKAGE__ ) ? @$_ : $_ }
61             map Types::TypeTiny::to_TypeTiny( $_ ),
62             @{
63 40087         62014 ref $opts{type_constraints} eq "ARRAY"
64             ? $opts{type_constraints}
65 40087 50       172361 : [ $opts{type_constraints} ]
66             }
67             ];
68            
69 40087         73823 if ( Type::Tiny::_USE_XS ) {
70 40087         56513 my @constraints = @{ $opts{type_constraints} };
  40087         89495  
71             my @known = map {
72 40087         72084 my $known = Type::Tiny::XS::is_known( $_->compiled_check );
  80209         176623  
73 80209 100       526584 defined( $known ) ? $known : ();
74             } @constraints;
75            
76 40087 100       114901 if ( @known == @constraints ) {
77 17         104 my $xsub = Type::Tiny::XS::get_coderef_for(
78             sprintf "AllOf[%s]",
79             join( ',', @known )
80             );
81 17 50       745 $opts{compiled_type_constraint} = $xsub if $xsub;
82             }
83             } #/ if ( Type::Tiny::_USE_XS)
84            
85 40087         147999 return $proto->SUPER::new( %opts );
86             } #/ sub new
87              
88             sub _lockdown {
89 40087     40087   81472 my ( $self, $callback ) = @_;
90 40087         127576 $callback->( $self->{type_constraints} );
91             }
92              
93 44     44 1 151 sub type_constraints { $_[0]{type_constraints} }
94 56   66 56 1 329 sub constraint { $_[0]{constraint} ||= $_[0]->_build_constraint }
95              
96 111     111   306 sub _is_null_constraint { 0 }
97              
98             sub _build_display_name {
99 41     41   85 my $self = shift;
100 41         114 join q[&], @$self;
101             }
102              
103             sub _build_constraint {
104 27     27   57 my @checks = map $_->compiled_check, @{ +shift };
  27         74  
105             return sub {
106 68     68   107 my $val = $_;
107 68   100     226 $_->( $val ) || return for @checks;
108 47         226 return !!1;
109             }
110 27         181 }
111              
112             sub can_be_inlined {
113 119     119 1 732 my $self = shift;
114 119         297 not grep !$_->can_be_inlined, @$self;
115             }
116              
117             sub inline_check {
118 114     114 1 258 my $self = shift;
119            
120 114 100       296 if ( Type::Tiny::_USE_XS and !exists $self->{xs_sub} ) {
121 34         81 $self->{xs_sub} = undef;
122            
123 34         45 my @constraints = @{ $self->type_constraints };
  34         87  
124             my @known = map {
125 34         71 my $known = Type::Tiny::XS::is_known( $_->compiled_check );
  71         184  
126 71 100       471 defined( $known ) ? $known : ();
127             } @constraints;
128            
129 34 100       113 if ( @known == @constraints ) {
130 7         33 $self->{xs_sub} = Type::Tiny::XS::get_subname_for(
131             sprintf "AllOf[%s]",
132             join( ',', @known )
133             );
134             }
135             } #/ if ( Type::Tiny::_USE_XS...)
136            
137 114         338 my $code = sprintf '(%s)', join " and ", map $_->inline_check( $_[0] ), @$self;
138            
139 114 100       1829 return "do { $Type::Tiny::SafePackage $code }"
140             if $Type::Tiny::AvoidCallbacks;
141             return "$self->{xs_sub}\($_[0]\)"
142 94 100       315 if $self->{xs_sub};
143 80         1861 return $code;
144             } #/ sub inline_check
145              
146             sub has_parent {
147 72     72 1 103 !!@{ $_[0]{type_constraints} };
  72         291  
148             }
149              
150             sub parent {
151 112     112 1 289 $_[0]{type_constraints}[0];
152             }
153              
154             sub validate_explain {
155 1     1 1 2 my $self = shift;
156 1         3 my ( $value, $varname ) = @_;
157 1 50       2 $varname = '$_' unless defined $varname;
158            
159 1 50       8 return undef if $self->check( $value );
160            
161 1         465 require Type::Utils;
162 1         7 for my $type ( @$self ) {
163 1         5 my $deep = $type->validate_explain( $value, $varname );
164             return [
165 1 50       12 sprintf(
166             '"%s" requires that the value pass %s',
167             $self,
168             Type::Utils::english_list( map qq["$_"], @$self ),
169             ),
170             @$deep,
171             ] if $deep;
172             } #/ for my $type ( @$self )
173            
174             # This should never happen...
175 0         0 return; # uncoverable statement
176             } #/ sub validate_explain
177              
178             my $_delegate = sub {
179             my ( $self, $method ) = ( shift, shift );
180             my @types = @{ $self->type_constraints };
181             my $found = 0;
182             for my $i ( 0 .. $#types ) {
183             my $type = $types[$i];
184             if ( $type->can( $method ) ) {
185             $types[$i] = $type->$method( @_ );
186             ++$found;
187             last;
188             }
189             }
190             _croak(
191             'Could not apply method %s to any type within the intersection',
192             $method
193             ) unless $found;
194             ref( $self )->new( type_constraints => \@types );
195             };
196              
197             sub stringifies_to {
198 1     1 1 9 my $self = shift;
199 1         4 $self->$_delegate( stringifies_to => @_ );
200             }
201              
202             sub numifies_to {
203 2     2 1 13 my $self = shift;
204 2         7 $self->$_delegate( numifies_to => @_ );
205             }
206              
207             sub with_attribute_values {
208 2     2 1 5 my $self = shift;
209 2         5 $self->$_delegate( with_attribute_values => @_ );
210             }
211              
212             my $comparator;
213             $comparator = sub {
214             my $A = shift->find_constraining_type;
215             my $B = shift->find_constraining_type;
216            
217             if ( $A->isa( __PACKAGE__ ) ) {
218             my @A_constraints = map $_->find_constraining_type, @{ $A->type_constraints };
219            
220             my @A_equal_to_B = grep $_->equals( $B ), @A_constraints;
221             if ( @A_equal_to_B == @A_constraints ) {
222             return Type::Tiny::CMP_EQUIVALENT();
223             }
224            
225             my @A_subs_of_B = grep $_->is_a_type_of( $B ), @A_constraints;
226             if ( @A_subs_of_B ) {
227             return Type::Tiny::CMP_SUBTYPE();
228             }
229             } #/ if ( $A->isa( __PACKAGE__...))
230            
231             elsif ( $B->isa( __PACKAGE__ ) ) {
232             my $r = $comparator->( $B, $A );
233             return $r if $r eq Type::Tiny::CMP_EQUIVALENT();
234             return -$r if $r eq Type::Tiny::CMP_SUBTYPE();
235             }
236            
237             return Type::Tiny::CMP_UNKNOWN();
238             };
239             push @Type::Tiny::CMP, $comparator;
240              
241             1;
242              
243             __END__
244              
245             =pod
246              
247             =encoding utf-8
248              
249             =head1 NAME
250              
251             Type::Tiny::Intersection - intersection type constraints
252              
253             =head1 SYNOPSIS
254              
255             Using via the C<< & >> operator overload:
256              
257             package Local::Stash {
258             use Moo;
259             use Types::Common qw( LowerCaseStr StrLength );
260            
261             has identifier => (
262             is => 'ro',
263             isa => (LowerCaseStr) & (StrLength[4, 8]),
264             );
265             }
266            
267             my $x = Local::Stash->new( data => {} ); # not ok
268             my $y = Local::Stash->new( data => [] ); # not ok
269              
270             Note that it is a good idea to enclose each type being intersected
271             in parentheses to avoid Perl thinking the C<< & >> is the sigil for
272             a coderef.
273              
274             Using Type::Tiny::Intersection's object-oriented interface:
275              
276             package Local::Stash {
277             use Moo;
278             use Types::Common qw( LowerCaseStr StrLength );
279             use Type::Tiny::Intersection;
280            
281             my $ShortLcStr = Type::Tiny::Intersection->new(
282             name => 'AnyData',
283             type_constraints => [ LowerCaseStr, StrLength[4, 8] ],
284             );
285            
286             has identifier => (
287             is => 'ro',
288             isa => $ShortLcStr,
289             );
290             }
291              
292             Using Type::Utils's functional interface:
293              
294             package Local::Stash {
295             use Moo;
296             use Types::Common qw( LowerCaseStr StrLength );
297             use Type::Utils;
298            
299             my $ShortLcStr = intersection ShortLcStr => [ LowerCaseStr, StrLength[4, 8] ];
300            
301             has identifier => (
302             is => 'ro',
303             isa => $ShortLcStr,
304             );
305             }
306              
307             =head1 STATUS
308              
309             This module is covered by the
310             L<Type-Tiny stability policy|Type::Tiny::Manual::Policies/"STABILITY">.
311              
312             =head1 DESCRIPTION
313              
314             Intersection type constraints.
315              
316             Intersection type constraints are not often very useful. Consider the
317             intersection of B<HashRef> and B<ArrayRef>. A value will only pass if
318             it is both a hashref and an arrayref. Given that neither of those type
319             constraints accept C<undef> or overloaded objects, there is no possible
320             value that can pass both.
321              
322             Which is not to say that intersections are never useful, but it happens
323             quite rarely.
324              
325             This package inherits from L<Type::Tiny>; see that for most documentation.
326             Major differences are listed below:
327              
328             =head2 Constructor
329              
330             The C<new> constructor from L<Type::Tiny> still works, of course. But there
331             is also:
332              
333             =over
334              
335             =item C<< new_by_overload(%attributes) >>
336              
337             Like the C<new> constructor, but will sometimes return another type
338             constraint which is not strictly an instance of L<Type::Tiny::Intersection>,
339             but still encapsulates the same meaning. This constructor is used by
340             Type::Tiny's overloading of the C<< & >> operator.
341              
342             =back
343              
344             =head2 Attributes
345              
346             =over
347              
348             =item C<type_constraints>
349              
350             Arrayref of type constraints.
351              
352             When passed to the constructor, if any of the type constraints in the
353             intersection is itself an intersection type constraint, this is "exploded"
354             into the new intersection.
355              
356             =item C<constraint>
357              
358             Unlike Type::Tiny, you I<cannot> pass a constraint coderef to the constructor.
359             Instead rely on the default.
360              
361             =item C<inlined>
362              
363             Unlike Type::Tiny, you I<cannot> pass an inlining coderef to the constructor.
364             Instead rely on the default.
365              
366             =item C<parent>
367              
368             Unlike Type::Tiny, you I<cannot> pass an inlining coderef to the constructor.
369             A parent will instead be automatically calculated.
370              
371             (Technically any of the types in the intersection could be treated as a
372             parent type; we choose the first arbitrarily.)
373              
374             =back
375              
376             =head2 Methods
377              
378             =over
379              
380             =item C<< stringifies_to($constraint) >>
381              
382             See L<Type::Tiny::ConstrainedObject>.
383              
384             =item C<< numifies_to($constraint) >>
385              
386             See L<Type::Tiny::ConstrainedObject>.
387              
388             =item C<< with_attribute_values($attr1 => $constraint1, ...) >>
389              
390             See L<Type::Tiny::ConstrainedObject>.
391              
392             =back
393              
394             =head2 Overloading
395              
396             =over
397              
398             =item *
399              
400             Arrayrefification calls C<type_constraints>.
401              
402             =back
403              
404             =head1 BUGS
405              
406             Please report any bugs to
407             L<https://github.com/tobyink/p5-type-tiny/issues>.
408              
409             =head1 SEE ALSO
410              
411             L<Type::Tiny::Manual>.
412              
413             L<Type::Tiny>.
414              
415             L<MooseX::Meta::TypeConstraint::Intersection>.
416              
417             =head1 AUTHOR
418              
419             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
420              
421             =head1 COPYRIGHT AND LICENCE
422              
423             This software is copyright (c) 2013-2014, 2017-2023 by Toby Inkster.
424              
425             This is free software; you can redistribute it and/or modify it under
426             the same terms as the Perl 5 programming language system itself.
427              
428             =head1 DISCLAIMER OF WARRANTIES
429              
430             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
431             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
432             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.