File Coverage

blib/lib/Types/ReadOnly.pm
Criterion Covered Total %
statement 44 46 95.6
branch 7 10 70.0
condition 9 15 60.0
subroutine 12 12 100.0
pod n/a
total 72 83 86.7


line stmt bran cond sub pod time code
1 6     6   739236 use 5.008;
  6         23  
  6         250  
2 6     6   36 use strict;
  6         12  
  6         212  
3 6     6   44 use warnings;
  6         12  
  6         438  
4              
5             package Types::ReadOnly;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.002';
9              
10 6     6   1118 use Type::Tiny 0.022 ();
  6         21918  
  6         154  
11 6     6   1220 use Types::Standard qw( Any Dict );
  6         57950  
  6         47  
12 6     6   9940 use Type::Utils;
  6         32833  
  6         56  
13 6     6   9180 use Type::Library -base, -declare => qw( ReadOnly Locked );
  6         13  
  6         42  
14              
15 6     6   3011 use Scalar::Util qw( reftype blessed );
  6         13  
  6         5120  
16              
17             sub _dclone($) {
18 1     1   3543 require Storable;
19 6     6   39 no warnings 'redefine';
  6         15  
  6         7579  
20 1         3419 *_dclone = \&Storable::dclone;
21 1         57 goto &Storable::dclone;
22             }
23              
24             my %skip = map { $_ => 1 } qw/CODE GLOB/;
25             sub _make_readonly {
26 7     7   1710 my (undef, $dont_clone) = @_;
27 7 50 66     77 if (my $reftype = reftype $_[0] and not blessed($_[0]) and not &Internals::SvREADONLY($_[0])) {
      66        
28 3 100 66     38 $_[0] = _dclone($_[0]) if !$dont_clone && &Internals::SvREFCNT($_[0]) > 1 && !$skip{$reftype};
      66        
29 3         12 &Internals::SvREADONLY($_[0], 1);
30 3 50 33     36 if ($reftype eq 'SCALAR' || $reftype eq 'REF') {
    100          
    50          
31 0         0 _make_readonly(${ $_[0] }, 1);
  0         0  
32             }
33             elsif ($reftype eq 'ARRAY') {
34 1         2 _make_readonly($_) for @{ $_[0] };
  1         7  
35             }
36             elsif ($reftype eq 'HASH') {
37 2         8 &Internals::hv_clear_placeholders($_[0]);
38 2         6 _make_readonly($_) for values %{ $_[0] };
  2         21  
39             }
40             }
41 7         20 Internals::SvREADONLY($_[0], 1);
42 7         26 return;
43             }
44              
45             our %READONLY_REF_TYPES = (HASH => 1, ARRAY => 1, SCALAR => 1, REF => 1);
46              
47             declare ReadOnly,
48             bless => 'Type::Tiny::Wrapper',
49             pre_check => sub
50             {
51             $READONLY_REF_TYPES{reftype($_)} and &Internals::SvREADONLY($_);
52             },
53             inlined_pre_check => sub
54             {
55             return (
56             "\$Types::ReadOnly::READONLY_REF_TYPES{Scalar::Util::reftype($_)}",
57             "&Internals::SvREADONLY($_)",
58             );
59             },
60             post_coerce => sub
61             {
62             _make_readonly($_);
63             return $_;
64             },
65             inlined_post_coerce => sub
66             {
67             "do { Types::ReadOnly::_make_readonly($_); $_ }";
68             };
69              
70             my $_FIND_KEYS = sub {
71             my ($dict) = grep {
72             $_->is_parameterized
73             and $_->has_parent
74             and $_->parent->strictly_equals(Dict)
75             } $_[0], $_[0]->parents;
76             return unless $dict;
77             return if ref($dict->parameters->[-1]) eq q(HASH);
78             my @keys = sort keys %{ +{ @{ $dict->parameters } } };
79             return unless @keys;
80             \@keys;
81             };
82              
83             # Stolen from Hash::Util 0.15.
84             # In earlier versions, of Hash::Util, there is only a hashref_unlocked
85             # function, which happens to be very broken. :-/
86             sub _hashref_locked
87             {
88 3     3   3247 my $hash=shift;
89 3         26 Internals::SvREADONLY(%$hash);
90             }
91              
92             declare Locked,
93             bless => 'Type::Tiny::Wrapper',
94             pre_check => sub
95             {
96             return unless reftype($_) eq 'HASH';
97             return unless &Internals::SvREADONLY($_);
98            
99             my $type = shift;
100             my $wrapped = $type->wrapped;
101            
102             if (my $KEYS = $wrapped->$_FIND_KEYS) {
103             require Hash::Util;
104             my $keys = join "*#*", @$KEYS;
105             my $legal = join "*#*", sort(&Hash::Util::legal_keys($_));
106             return if $keys ne $legal;
107             }
108            
109             return !!1;
110             },
111             inlined_pre_check => sub
112             {
113             my @r;
114             push @r, qq[Scalar::Util::reftype($_) eq 'HASH'];
115             push @r, qq[&Internals::SvREADONLY($_)];
116            
117             my $type = $_[0];
118             my $wrapped = $type->wrapped;
119            
120             if (my $KEYS = $wrapped->$_FIND_KEYS) {
121             require B;
122             require Hash::Util;
123             push @r, B::perlstring(join "*#*", @$KEYS)
124             .qq[ eq join("*#*", sort(&Hash::Util::legal_keys($_)))||''];
125             }
126            
127             return @r;
128             },
129             post_coerce => sub
130             {
131             require Hash::Util;
132            
133             my $type = shift;
134             my $wrapped = $type->wrapped;
135            
136             &Hash::Util::unlock_hash($_);
137             &Hash::Util::lock_keys($_, @{ $wrapped->$_FIND_KEYS || [] });
138             return $_;
139             },
140             inlined_post_coerce => sub
141             {
142             require Hash::Util;
143            
144             my $type = shift;
145             my $wrapped = $type->wrapped;
146            
147             my $qkeys = '';
148             if (my $KEYS = $wrapped->$_FIND_KEYS) {
149             require B;
150             $qkeys = join q[,], '', map B::perlstring($_), @$KEYS;
151             }
152            
153             "&Hash::Util::unlock_hash($_); &Hash::Util::lock_keys($_ $qkeys); $_;";
154             };
155              
156             1;
157              
158             __END__