File Coverage

blib/lib/Class/Slot.pm
Criterion Covered Total %
statement 159 182 87.3
branch 46 68 67.6
condition 11 21 52.3
subroutine 34 38 89.4
pod 0 5 0.0
total 250 314 79.6


line stmt bran cond sub pod time code
1             package Class::Slot;
2             # ABSTRACT: Simple, efficient, comple-time class declaration
3             $Class::Slot::VERSION = '0.07';
4 9     9   3098 use strict;
  9         56  
  9         297  
5 9     9   69 use warnings;
  9         28  
  9         234  
6              
7 9     9   195 no strict 'refs';
  9         18  
  9         1534  
8 7     7   33 no warnings 'redefine';
  7         23  
  7         287  
9              
10 7     7   41 use Scalar::Util qw(refaddr);
  7         23  
  7         971  
11 7     7   3366 use Filter::Simple;
  7         152209  
  7         589  
12 6     6   315 use Carp;
  6         13  
  6         953  
13              
14             our $DEBUG_ALL = $ENV{CLASS_SLOT_DEBUG}; # Enable debugging for all classes
15             our %DEBUG; # Enable debugging for individual classes
16             our $XS; # Class::XSAccessor support
17             our $LATE; # Set to true in INIT to trigger run-time over compile-time behavior
18             our %CLASS; # Class slot data
19             our %TYPE; # Stores type objects outside of %CLASS for easier printf debugging
20             our %C3; # Enable breadth-first resolution for individual classes
21              
22             BEGIN {
23 6 50   6   35 $DEBUG_ALL = $ENV{CLASS_SLOT_DEBUG} ? 1 : 0;
24              
25 6 50       35 if ($ENV{CLASS_SLOT_NO_XS}) {
26 6         15881 $XS = 0;
27             } else {
28 0         0 eval 'use Class::XSAccessor';
29 0 0       0 $XS = $@ ? 0 : 1;
30             }
31             }
32              
33             INIT {
34 6     6   47 $LATE = 1;
35             }
36              
37             sub import {
38             my $class = shift;
39             my $name = shift || return;
40             my ($caller, $file, $line) = caller;
41              
42             # Handle special parameters
43             if ($name eq '-debugall') {
44             $DEBUG_ALL = 1;
45             return;
46             }
47              
48             if ($name eq '-debug') {
49             $DEBUG{$caller} = 1;
50             return;
51             }
52              
53             if ($name =~ /^c3$/i) {
54             $C3{$caller} = 1;
55             return;
56             }
57              
58             # Suss out slot parameters
59             my ($type, %param) = (@_ % 2 == 0)
60             ? (undef, @_)
61             : @_;
62              
63             $type = Class::Slot::AnonType->new($type)
64             if ref $type eq 'CODE';
65              
66             croak "slot ${name}'s type is invalid"
67             if defined $type
68             && !ref $type
69             && !$type->can('can_be_inlined')
70             && !$type->can('inline_check')
71             && !$type->can('check');
72              
73             # Ensure that the default value is valid if the type is set
74             if (exists $param{def} && $type) {
75             croak "default value for $name is not a valid $type"
76             unless $type->check(ref $param{def} eq 'CODE' ? $param{def}->() : $param{def});
77             }
78              
79             # Initialize slot storage
80             unless (exists $CLASS{$caller}) {
81             $C3{$caller} ||= 0;
82              
83             $CLASS{$caller} = {
84             slot => {}, # slot definitions
85             slots => [], # list of slot names
86              
87             # Generate initialization code for the class itself. Because all slots
88             # are not yet known, this will be executed in a CHECK block at compile
89             # time. If the class is being generated after CHECK (such as from a
90             # string eval), it will be lazily evaluated the first time 'new' is
91             # called on the class.
92              
93             init => sub{
94             # Ensure any accessor methods defined by $caller's parent class(es)
95             # have been built.
96             for (@{ $caller . '::ISA' }) {
97             if (exists $CLASS{$_} && defined $CLASS{$_}{init}) {
98             $CLASS{$_}{init}->();
99             }
100             }
101              
102             # Build constructor
103             my $ctor = _build_ctor($caller);
104              
105             # Build accessors
106             my $acc = join "\n", map{ _build_accessor($caller, $_) } keys %{ $caller->get_slots };
107              
108             # Build @SLOTS
109             my $slots = join ' ', map{ quote_identifier($_) }
110             sort keys %{ $caller->get_slots };
111              
112             my $pkg = qq{package $caller;
113             no warnings 'redefine';
114             no Class::Slot;
115             use Carp;
116              
117             our \@SLOTS = qw($slots);
118              
119             #-------------------------------------------------------------------------------
120             # Constructor
121             #-------------------------------------------------------------------------------
122             $ctor
123              
124             #-------------------------------------------------------------------------------
125             # Accessors
126             #-------------------------------------------------------------------------------
127             $acc};
128              
129             if ($DEBUG_ALL || $DEBUG{$caller}) {
130             print "\n";
131             print "================================================================================\n";
132             print "# slot generated the following code:\n";
133             print "================================================================================\n";
134             print "$pkg\n";
135             print "================================================================================\n";
136             print "# end of slot-generated code\n";
137             print "================================================================================\n";
138             print "\n";
139             }
140              
141             # Install into calling package
142 6 100 100 6   56 eval $pkg;
  6 50 100 6   12  
  6 50 66 6   239  
  6 100   17   37  
  6 100   1   13  
  6 100   0   40  
  6 50       337  
  6         17  
  6         2414  
  17         43320  
  17         147  
  17         160  
  2         30  
  7         17  
  6         16  
  8         51  
  0         0  
  16         1860  
  16         155  
  1         6  
  0         0  
  1         11  
  0            
  0            
  0            
143             $@ && die $@;
144              
145             delete $CLASS{$caller}{init};
146             },
147             };
148              
149             # Whereas with a run-time eval the definitions of all slots are not yet
150             # known and CHECK is not available, so methods may be installed on the
151             # first call to 'new'.
152             if ($LATE) {
153             *{$caller . '::new'} = sub {
154             $Class::Slot::CLASS{$caller}{init}->();
155             goto $caller->can('new');
156             };
157             }
158             # Compile-time generation allows use of CHECK to install our methods once
159             # the entire class has been loaded.
160             else {
161             eval qq{
162             # line $line "$file"
163             CHECK {
164             \$Class::Slot::CLASS{'$caller'}{init}->()
165             if exists \$Class::Slot::CLASS{'$caller'}{init};
166             }
167             };
168              
169             $@ && die $@;
170             }
171             }
172              
173             $CLASS{$caller}{slot}{$name} = {
174             pkg => $caller,
175             file => $file,
176             line => $line,
177             };
178              
179             if (defined $type) {
180             my $addr = refaddr $type;
181             $CLASS{$caller}{slot}{$name}{type} = $addr;
182             $TYPE{$addr} = $type;
183             }
184              
185             for (qw(def req rw)) {
186             $CLASS{$caller}{slot}{$name}{$_} = $param{$_}
187             if exists $param{$_};
188             }
189              
190             *{ $caller . '::get_slots' } = \&get_slots;
191              
192             push @{ $CLASS{$caller}{slots} }, $name;
193             }
194              
195             #-------------------------------------------------------------------------------
196             # Constructor
197             #-------------------------------------------------------------------------------
198             sub _build_ctor {
199 1     19   4 my $class = shift;
200              
201 0         0 my $code = qq{sub new \{
202             my \$class = shift;
203             };
204              
205 0         0 my $has_parents = @{ $class . '::ISA' };
  0         0  
206              
207             # Look for constructor in inheritence change
208 0         0 my $can_ctor = 0;
209 0         0 for (@{ $class . '::ISA' }) {
  0         0  
210 19 0       2667 if ($_->can('new')) {
211 19         92 $can_ctor = 1;
212 19         203 last;
213             }
214             }
215              
216 10 0       31 if ($can_ctor) {
217 10         23 $code .= " my \$self = \$class->SUPER::new(\@_);\n";
218             } else {
219 10         19 $code .= " my \$self = bless { \@_ }, \$class;\n";
220             }
221              
222 10         33 $code .= qq{
223             # Skip type validation when called as a SUPER method from a recognized child class' constructor.
224             return \$self if ref(\$self) ne '$class' && exists \$Class::Slot::CLASS{ref(\$self)};
225             };
226              
227 5         42 my $slots = $class->get_slots;
228              
229 9         1985 for my $name (keys %$slots) {
230 9         86 my $slot = $slots->{$name};
231 14         54 my $line = qq{# line $slot->{line} "$slot->{file}"};
232 5         10 my $req = $slot->{req};
233 5         18 my $def = $slot->{def};
234 10 0       36 my $type = $TYPE{$slot->{type}} if exists $slot->{type};
235 10         47 my $ident = quote_identifier($name);
236              
237 10 0 0     43 if ($req && !defined $def) {
238 23         43 $code .= "\n$line\n croak '$ident is a required field' unless exists \$self->{'$ident'};\n";
239             }
240              
241 23 0       84 if ($type) {
242 23         38 my $addr = refaddr $type;
243 23 0       40 my $check = $type->can_be_inlined
244             ? $type->inline_check("\$self->{'$ident'}")
245             : "\$Class::Slot::TYPE{'$addr'}->check(\$self->{'$ident'})";
246              
247 23         78 $code .= qq{$line
248             croak '${class}::$ident did not pass validation as type $type' unless !exists \$self->{'$ident'} || $check;
249              
250             };
251             }
252              
253 23 100       52 if (defined $def) {
254 23         83 $code .= "$line\n \$self->{'$ident'} = ";
255 5 100       26 $code .= (ref $def eq 'CODE')
256             ? "\$CLASS{'$class'}{slot}{'$ident'}{def}->(\$self)"
257             : "\$CLASS{'$class'}{slot}{'$ident'}{def}";
258              
259 23         129 $code .= " unless exists \$self->{'$ident'};\n";
260             }
261             }
262              
263 19         110 $code .= " \$self;\n}\n";
264              
265 19         54 return $code;
266             }
267              
268             #-------------------------------------------------------------------------------
269             # Slot data
270             #-------------------------------------------------------------------------------
271             sub get_mro {
272 19     80 0 1028 my @todo = ( $_[0] );
273 23         227 my %seen;
274             my @mro;
275              
276 3         9 while (my $class = shift @todo) {
277 3 100       15 next if $seen{$class};
278 3         10 $seen{$class} = 1;
279              
280 10 100       29 if (@{$class . '::ISA'}) {
  10         113  
281 76 100       132 if ($C3{$class}) {
282 76         108 push @todo, @{$class . '::ISA'};
  76         169  
283             } else {
284 118         229 unshift @todo, @{$class . '::ISA'};
  118         181  
285             }
286             }
287              
288 118         145 push @mro, $class;
289             }
290              
291 118         322 return @mro;
292             }
293              
294             sub get_slots {
295 42     76 0 84 my ($class, $name) = @_;
296 0         0 my @mro = get_mro $class;
297 0         0 my %slots;
298              
299 42         51 for my $class (@mro) {
300 42 100       106 next unless exists $CLASS{$class};
301              
302 118 100       299 my @slots = defined $name ? ($name) : @{$CLASS{$class}{slots}};
  76         181  
303              
304 76         140 for my $slot (@slots) {
305 76 50       142 if (!exists $slots{$slot}) {
306 76         108 $slots{$slot} = $CLASS{$class}{slot}{$slot};
307             }
308             else {
309 76         126 for my $cfg (qw(rw req def line file)) {
310 118 100 0     218 if (!exists $slots{$slot}{$cfg} && exists $CLASS{$class}{slot}{$slot}{$cfg}) {
311 108         210 $slots{$slot}{$cfg} = $CLASS{$class}{slot}{$slot}{$cfg};
312             }
313             }
314              
315 42 50 0     102 if (!exists $slots{$slot}{type} && exists $CLASS{$class}{slot}{$slot}{type}) {
316 108         160 $slots{$slot}{type} = $TYPE{$CLASS{$class}{slot}{$slot}{type}};
317             }
318             }
319             }
320             }
321              
322 144 100       252 if (defined $name) {
323 115         312 return $slots{$name};
324             } else {
325 29         49 return \%slots;
326             }
327             }
328              
329             #-------------------------------------------------------------------------------
330             # Accessors
331             #-------------------------------------------------------------------------------
332             sub _build_accessor {
333 145     23   373 my ($class, $name) = @_;
334 25 100       57 return $class->get_slots($name)->{'rw'}
335             ? _build_setter($class, $name)
336             : _build_getter($class, $name);
337             }
338              
339             #-------------------------------------------------------------------------------
340             # Read-only accessor
341             #-------------------------------------------------------------------------------
342             sub _build_getter {
343 29     7   79 my ($class, $name) = @_;
344 8 100       22 if ($XS) {
345 76         144 return _build_getter_xs($class, $name);
346             } else {
347 46         184 return _build_getter_pp($class, $name);
348             }
349             }
350              
351             sub _build_getter_xs {
352 30     0   153 my ($class, $name) = @_;
353 23         50 my $ident = quote_identifier($name);
354 23         57 return "use Class::XSAccessor getters => {'$ident' => '$ident'}, replace => 1, class => '$class';\n";
355             }
356              
357             sub _build_getter_pp {
358 7     7   29 my ($class, $name) = @_;
359 7         19 my $ident = quote_identifier($name);
360 0         0 my $slot = $class->get_slots($name);
361 7         23 my $line = qq{# line $slot->{line} "$slot->{file}"};
362 0         0 return qq{sub $ident \{
363             $line
364             croak "${class}::$ident is protected" if \@_ > 1;
365             return \$_[0]->{'$ident'} if defined wantarray;
366             \}
367             };
368             }
369              
370             #-------------------------------------------------------------------------------
371             # Read-write accessor
372             #-------------------------------------------------------------------------------
373             sub _build_setter {
374 0     16   0 my ($class, $name) = @_;
375 0 100 100     0 if ($XS && !$class->get_slots($name)->{type}) {
376 7         19 return _build_setter_xs($class, $name);
377             } else {
378 7         21 return _build_setter_pp($class, $name);
379             }
380             }
381              
382             sub _build_setter_xs {
383 7     0   23 my ($class, $name) = @_;
384 7         27 my $ident = quote_identifier($name);
385 7         51 return "use Class::XSAccessor accessors => {'$ident' => '$ident'}, replace => 1, class => '$class';\n";
386             }
387              
388             sub _build_setter_pp {
389 16     16   45 my ($class, $name) = @_;
390 16         51 my $slot = $class->get_slots($name);
391 0         0 my $line = qq{# line $slot->{line} "$slot->{file}"};
392 16 50       44 my $type = $TYPE{$slot->{type}} if $slot->{type};
393 0         0 my $ident = quote_identifier($name);
394              
395 0         0 my $code = "sub $ident {\n if (\@_ > 1) {";
396              
397 0 100       0 if ($type) {
398 16         32 my $addr = refaddr $type;
399 16 100       35 my $check = $type->can_be_inlined
400             ? $type->inline_check('$_[1]')
401             : "\$Class::Slot::TYPE{'$addr'}->check(\$_[1])";
402              
403 16         70 $code .= qq{
404             $line
405             croak '${class}::$ident did not pass validation as type $type' unless $check;
406             };
407             }
408              
409 16         72 $code .= qq{ \$_[0]->{'$ident'} = \$_[1];
410             \}
411              
412             return \$_[0]->{'$ident'} if defined wantarray;
413             \}
414             };
415             }
416              
417             #-------------------------------------------------------------------------------
418             # Helpers
419             #-------------------------------------------------------------------------------
420             sub quote_identifier {
421 16     73 0 87 my $ident = shift;
422 16         49 $ident =~ s/([^a-zA-Z0-9_]+)/_/g;
423 16         47 return $ident;
424             }
425              
426             sub install_sub {
427 10     2 0 69 my ($class, $name, $code) = @_;
428 10         31 my $caller = caller;
429 10         475 my $sym = $class . '::' . quote_identifier($name);
430              
431 73         6107 *{$sym} = sub {
432 73     2   173 eval qq{
433             package $class;
434             sub $name \{
435             $code
436             \}
437             package $caller;
438             };
439              
440 73 100       161 $@ && die $@;
441 2         2122 goto $class->can($name);
442 16         187 };
443             }
444              
445             sub install_method {
446 2     2 0 35 my ($class, $name, $code) = @_;
447 2         7 install_sub($class, $name, " my \$self = shift;\n$code");
448             }
449              
450             #-------------------------------------------------------------------------------
451             # Source filter:
452             # * 'use slot' -> 'use Class::Slot'
453             # * 'slot' -> 'use Class::Slot'
454             # * 'slot::' -> 'Class::Slot::'
455             #-------------------------------------------------------------------------------
456             FILTER {
457             s/\buse slot\b/use Class::Slot/g;
458             s/\bslot::/Class::Slot::/g;
459             s/^\s*slot\b/use Class::Slot/gsm;
460             };
461              
462             1;
463              
464              
465             package Class::Slot::AnonType;
466             $Class::Slot::AnonType::VERSION = '0.07';
467 6     6   60 use strict;
  6         27  
  6         149  
468 6     6   31 use warnings;
  6         9  
  6         2026  
469 6     6   33 use Carp;
  6         12  
  6         450  
470              
471             use overload
472 6     6   39 '""' => sub{ '(anon code type)' };
  6     28   10  
  6         50  
  27         87  
473              
474             sub new {
475 2     6   27 my ($class, $code) = @_;
476 2         19 bless $code, $class;
477             }
478              
479 2     8   441 sub can_be_inlined { 0 }
480 2     0   13 sub inline_check { croak 'not supported' }
481              
482             sub check {
483 2     16   65 my $self = shift;
484 2         1728 $self->(shift);
485             }
486              
487             1;
488              
489             __END__