File Coverage

blib/lib/Type/Tie.pm
Criterion Covered Total %
statement 146 150 97.3
branch 33 42 78.5
condition 10 19 52.6
subroutine 45 48 93.7
pod 1 1 100.0
total 235 260 90.3


line stmt bran cond sub pod time code
1 9     9   155582 use 5.008001;
  9         41  
2 9     9   54 use strict;
  9         43  
  9         315  
3 9     9   93 use warnings;
  9         18  
  9         581  
4              
5 9     9   52 use Carp ();
  9         18  
  9         207  
6 9     9   4065 use Exporter::Tiny ();
  9         47715  
  9         275  
7 9     9   68 use Scalar::Util ();
  9         15  
  9         33092  
8              
9             ++$Carp::CarpInternal{"Type::Tie::$_"} for qw( BASE SCALAR ARRAY HASH );
10              
11             {
12             package Type::Tie;
13             our $AUTHORITY = 'cpan:TOBYINK';
14             our $VERSION = '2.010001';
15             our @ISA = qw( Exporter::Tiny );
16             our @EXPORT = qw( ttie );
17            
18             $VERSION =~ tr/_//d;
19            
20             sub ttie (\[$@%]@)#>&%*/&<%\$[]^!@;@)
21             {
22 17     17 1 1683532 my ( $ref, $type, @vals ) = @_;
23            
24 17 100       100 if ( 'HASH' eq ref $ref ) {
    100          
25 5         57 tie %$ref, "Type::Tie::HASH", $type, @vals;
26             }
27             elsif ( 'ARRAY' eq ref $ref ) {
28 5         55 tie @$ref, "Type::Tie::ARRAY", $type, @vals;
29             }
30             else {
31 7         90 tie $$ref, "Type::Tie::SCALAR", $type, @vals;
32             }
33 17         55 return $ref;
34             }
35             };
36              
37             {
38             package Type::Tie::BASE;
39             our $AUTHORITY = 'cpan:TOBYINK';
40             our $VERSION = '2.010001';
41            
42             $VERSION =~ tr/_//d;
43            
44             # Type::Tie::BASE is an array-based object. If you need to subclass it
45             # and store more attributes, use $yourclass->SUPER::_NEXT_SLOT to find
46             # the next available slot, then override _NEXT_SLOT so that other people
47             # can subclass your class too.
48             #
49             sub _REF { $_[0][0] } # ro
50             sub _TYPE { ( @_ == 2 ) ? ( $_[0][1] = $_[1] ) : $_[0][1] } # rw
51             sub _CHECK { ( @_ == 2 ) ? ( $_[0][2] = $_[1] ) : $_[0][2] } # rw
52             sub _COERCE { ( @_ == 2 ) ? ( $_[0][3] = $_[1] ) : $_[0][3] } # rw
53 1     1   4 sub _NEXT_SLOT { 4 }
54            
55             sub type { shift->_TYPE }
56 3   33 3   16 sub _INIT_REF { $_[0][0] ||= $_[0]->_DEFAULT }
57            
58             {
59             my $try_xs =
60             exists( $ENV{PERL_TYPE_TINY_XS} ) ? !!$ENV{PERL_TYPE_TINY_XS} :
61             exists( $ENV{PERL_ONLY} ) ? !$ENV{PERL_ONLY} :
62             !!1;
63             eval {
64             require Class::XSAccessor::Array;
65             'Class::XSAccessor::Array'->import(
66             replace => !!1,
67             getters => { _REF => 0, type => 1 },
68             accessors => { _TYPE => 1, _CHECK => 2, _COERCE => 3 },
69             );
70             } if $try_xs;
71             }
72            
73             sub _set_type {
74 27     27   55 my $self = shift;
75 27         56 my $type = $_[0];
76            
77 27         214 $self->_TYPE( $type );
78            
79 27 100 66     247 if ( Scalar::Util::blessed( $type ) and $type->isa( 'Type::Tiny' ) ) {
80 23         110 $self->_CHECK( $type->compiled_check );
81 23 100       98 $self->_COERCE(
82             $type->has_coercion
83             ? $type->coercion->compiled_coercion
84             : undef
85             );
86             }
87             else {
88             $self->_CHECK(
89             $type->can( 'compiled_check' )
90             ? $type->compiled_check
91 26     26   43 : sub { $type->check( $_[0] ) }
92 4 50       45 );
93             $self->_COERCE(
94             $type->can( 'has_coercion' ) && $type->can( 'coerce' ) && $type->has_coercion
95 20     20   34 ? sub { $type->coerce( $_[0] ) }
96             : undef
97 4 100 66     35 );
98             }
99             }
100            
101             # Only used if the type has no get_message method
102             sub _dd {
103 1     1   783 require Type::Tiny;
104 1         6 goto \&Type::Tiny::_dd;
105             }
106            
107             sub coerce_and_check_value {
108 83     83   154 my $self = shift;
109 83         176 my $check = $self->_CHECK;
110 83         153 my $coerce = $self->_COERCE;
111            
112             my @vals = map {
113 83 100       238 my $val = $coerce ? $coerce->( $_ ) : $_;
  102         263  
114 102 100       526 if ( not $check->( $val ) ) {
115 26         100 my $type = $self->_TYPE;
116 26 100 66     159 Carp::croak(
      50        
117             $type && $type->can( 'get_message' )
118             ? $type->get_message( $val )
119             : sprintf( '%s does not meet type constraint %s', _dd($_), $type || 'Unknown' )
120             );
121             }
122 76         277 $val;
123             } ( my @cp = @_ ); # need to copy @_ for Perl < 5.14
124            
125 57 100       285 wantarray ? @vals : $vals[0];
126             }
127            
128             # store the $type for the exiting instances so the type can be set
129             # (uncloned) in the clone too. A clone process could be cloning several
130             # instances of this class, so use a hash to hold the types during
131             # cloning. These types are reference counted, so the last reference to
132             # a particular type deletes its key.
133             my %tmp_clone_types;
134             sub STORABLE_freeze {
135 3     3   213 my ( $o, $cloning ) = @_;
136 3 50       12 Carp::croak( "Storable::freeze only supported for dclone-ing" )
137             unless $cloning;
138            
139 3         10 my $type = $o->_TYPE;
140 3         8 my $refaddr = Scalar::Util::refaddr( $type );
141 3   50     25 $tmp_clone_types{$refaddr} ||= [ $type, 0 ];
142 3         6 ++$tmp_clone_types{$refaddr}[1];
143            
144 3         303 return ( $refaddr, $o->_REF );
145             }
146            
147             sub STORABLE_thaw {
148 3     3   14 my ( $o, $cloning, $refaddr, $o2 ) = @_;
149 3 50       11 Carp::croak( "Storable::thaw only supported for dclone-ing" )
150             unless $cloning;
151            
152 3         16 $o->_THAW( $o2 ); # implement in child classes
153            
154 3         9 my $type = $tmp_clone_types{$refaddr}[0];
155             --$tmp_clone_types{$refaddr}[1]
156 3 50       13 or delete $tmp_clone_types{$refaddr};
157 3         10 $o->_set_type($type);
158             }
159             };
160              
161             {
162             package Type::Tie::ARRAY;
163             our $AUTHORITY = 'cpan:TOBYINK';
164             our $VERSION = '2.010001';
165             our @ISA = qw( Type::Tie::BASE );
166            
167             $VERSION =~ tr/_//d;
168            
169             sub TIEARRAY {
170 7     7   21 my $class = shift;
171 7         15 my $type = shift;
172 7         41 my $self = bless( [ $class->_DEFAULT ], $class );
173 7         55 $self->_set_type( $type );
174 7 100       54 if ( @_ ) {
175 4         27 my $R = $self->_REF;
176 4         13 @$R = map { $self->coerce_and_check_value( $_ ) } @_;
  12         37  
177             }
178 7         34 $self;
179             }
180 8     8   45 sub _DEFAULT { [] }
181 68     68   4333 sub FETCHSIZE { scalar @{ $_[0]->_REF } }
  68         145  
182 1     1   7 sub STORESIZE { $#{ $_[0]->_REF } = $_[1] }
  1         5  
183 8     8   51 sub STORE { $_[0]->_REF->[ $_[1] ] = $_[0]->coerce_and_check_value( $_[2] ) }
184 57     57   245 sub FETCH { $_[0]->_REF->[ $_[1] ] }
185 0     0   0 sub CLEAR { @{ $_[0]->_REF } = () }
  0         0  
186 1     1   8 sub POP { pop @{ $_[0]->_REF } }
  1         5  
187 11     11   1317 sub PUSH { my $s = shift; push @{$s->_REF}, $s->coerce_and_check_value( @_ ) }
  11         19  
  11         83  
188 3     3   5 sub SHIFT { shift @{ $_[0]->_REF } }
  3         7  
189 7     7   37 sub UNSHIFT { my $s = shift; unshift @{$s->_REF}, $s->coerce_and_check_value( @_ ) }
  7         13  
  7         31  
190 1     1   268 sub EXISTS { exists $_[0]->_REF->[ $_[1] ] }
191 1     1   6 sub DELETE { delete $_[0]->_REF->[ $_[1] ] }
192       0     sub EXTEND {}
193             sub SPLICE {
194 2     2   6 my $o = shift;
195 2         3 my $sz = scalar @{$o->_REF};
  2         8  
196 2 50       8 my $off = @_ ? shift : 0;
197 2 50       5 $off += $sz if $off < 0;
198 2 50       4 my $len = @_ ? shift : $sz-$off;
199 2         3 splice @{$o->_REF}, $off, $len, $o->coerce_and_check_value( @_ );
  2         7  
200             }
201 1     1   3 sub _THAW { @{ $_[0]->_INIT_REF } = @{ $_[1] } }
  1         7  
  1         3  
202             };
203              
204             {
205             package Type::Tie::HASH;
206             our $AUTHORITY = 'cpan:TOBYINK';
207             our $VERSION = '2.010001';
208             our @ISA = qw( Type::Tie::BASE );
209            
210             $VERSION =~ tr/_//d;
211            
212             sub TIEHASH {
213 7     7   21 my $class = shift;
214 7         17 my $type = shift;
215 7         42 my $self = bless( [ $class->_DEFAULT ], $class );
216 7         70 $self->_set_type( $type );
217 7 100       34 if ( @_ ) {
218 4         18 my $R = $self->_REF;
219 4         42 my %H = @_;
220 4         11 %$R = ();
221 4         22 while ( my ( $K, $V ) = each %H ) {
222 5         37 $R->{$K} = $self->coerce_and_check_value( $V );
223             }
224             }
225 7         34 $self;
226             }
227 8     8   32 sub _DEFAULT { +{} }
228 13     13   2188 sub STORE { $_[0]->_REF->{ $_[1] } = $_[0]->coerce_and_check_value( $_[2] ) }
229 13     13   737 sub FETCH { $_[0]->_REF->{ $_[1] } }
230 8     8   1802 sub FIRSTKEY { my $a = scalar keys %{ $_[0]->_REF }; each %{ $_[0]->_REF } }
  8         36  
  8         18  
  8         62  
231 13     13   18 sub NEXTKEY { each %{ $_[0]->_REF } }
  13         48  
232 13     13   168 sub EXISTS { exists $_[0]->_REF->{ $_[1] } }
233 3     3   20 sub DELETE { delete $_[0]->_REF->{ $_[1] } }
234 0     0   0 sub CLEAR { %{ $_[0]->_REF } = () }
  0         0  
235 1     1   728 sub SCALAR { scalar %{ $_[0]->_REF } }
  1         8  
236 1     1   2 sub _THAW { %{ $_[0]->_INIT_REF } = %{ $_[1] } }
  1         9  
  1         4  
237             };
238              
239             {
240             package Type::Tie::SCALAR;
241             our $AUTHORITY = 'cpan:TOBYINK';
242             our $VERSION = '2.010001';
243             our @ISA = qw( Type::Tie::BASE );
244            
245             $VERSION =~ tr/_//d;
246            
247             sub TIESCALAR {
248 10     10   30 my $class = shift;
249 10         24 my $type = shift;
250 10         149 my $self = bless( [ $class->_DEFAULT ], $class );
251 10         88 $self->_set_type($type);
252 10 100 33     58 if ( @_ ) {
    50          
253 6 50       26 Carp::croak( 'Too many initial values provided for SCALAR' ) if @_ > 1;
254 6         41 ${ $self->_REF } = $self->coerce_and_check_value( $_[0] );
  6         38  
255             }
256             elsif ( $type->can('type_default') and my $d = $type->type_default ) {
257 4         28 ${ $self->_REF } = $d->();
  4         28  
258             }
259 10         53 $self;
260             }
261 11     11   28 sub _DEFAULT { my $x; \$x }
  11         39  
262 19     19   7157 sub STORE { ${ $_[0]->_REF } = $_[0]->coerce_and_check_value( $_[1] ) }
  12         73  
263 17     17   1509 sub FETCH { ${ $_[0]->_REF } }
  17         438  
264 1     1   3 sub _THAW { ${ $_[0]->_INIT_REF } = ${ $_[1] } }
  1         7  
  1         3  
265             };
266              
267             1;
268              
269             __END__