File Coverage

blib/lib/Class/Slot.pm
Criterion Covered Total %
statement 196 213 92.0
branch 56 76 73.6
condition 13 21 61.9
subroutine 42 45 93.3
pod 0 6 0.0
total 307 361 85.0


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.09';
4 16     16   4301 use strict;
  16         103  
  16         525  
5 15     15   121 use warnings;
  15         27  
  15         277  
6              
7 15     15   386 no strict 'refs';
  15         26  
  15         2755  
8 8     8   35 no warnings 'redefine';
  8         14  
  8         399  
9              
10 8     8   49 use Scalar::Util qw(refaddr);
  8         15  
  8         1088  
11 8     8   4180 use Filter::Simple;
  8         197405  
  8         50  
12 8     8   425 use Carp;
  8         16  
  8         1306  
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 8 50   8   50 $DEBUG_ALL = $ENV{CLASS_SLOT_DEBUG} ? 1 : 0;
24              
25 8 100       27 if ($ENV{CLASS_SLOT_NO_XS}) {
26 7         23206 $XS = 0;
27             } else {
28 1         79 eval 'use Class::XSAccessor';
29 1 50       3276 $XS = $@ ? 0 : 1;
30             }
31             }
32              
33             sub is_runtime {
34 42 100   42 0 137 unless ($LATE) {
35             local $SIG{__WARN__} = sub{
36 4     4   25 my $msg = shift;
37              
38 4 50       1012 if ($msg =~ /Too late to run CHECK block/) {
39 3         7 $LATE = 1;
40              
41             # When multiple packages are defined in a single file or top-level
42             # string eval, they will generate a definition before CHECK is called.
43             # If they refer to each other, one may call a method of the other
44             # before the class' init has been called.
45             #
46             # To handle this case, we scan the %CLASS definitions for classes which
47             # have been defined but not yet initialized - that is, they are in
48             # %CLASS but the 'init' function hasn't been run yet (it deletes itself
49             # when it completes) - and then run those classes' initializers.
50 3         14 for my $class (keys %CLASS) {
51 3 50       14 next unless exists $Class::Slot::CLASS{$class}{init};
52              
53 0         0 *{$class.'::new'} = sub {
54 0         0 $Class::Slot::CLASS{$class}{init}->();
55 0         0 goto $class->can('new');
56 0         0 };
57             }
58              
59 3         67 return;
60             }
61              
62 0         0 CORE::warn($msg);
63 37         299 };
64              
65 7     7   1112201 eval 'CHECK{ 1 }';
  7     7   81  
  7     7   1777  
  5     5   1762  
  3     3   1784  
  36         1774  
66             }
67              
68 41         198 return $LATE;
69             }
70              
71             sub import {
72             my $class = shift;
73             my $name = shift;
74             my ($caller, $file, $line) = caller;
75              
76             if (is_runtime() && !(ref *{$caller.'::slot'} eq 'CODE')) {
77             *{$caller.'::slot'} = sub{
78 2         20 unshift @_, 'Class::Slot';
79 0           goto \&import;
80             };
81             }
82              
83             # Initialize the class
84             unless (exists $CLASS{$caller}) {
85             $C3{$caller} ||= 0;
86              
87             *{ $caller . '::get_slots' } = \&get_slots;
88              
89             $CLASS{$caller} = {
90             slot => {}, # slot definitions
91             slots => [], # list of slot names
92              
93             # Generate initialization code for the class itself. Because all slots
94             # are not yet known, this will be executed in a CHECK block at compile
95             # time. If the class is being generated after CHECK (such as from a
96             # string eval), it will be lazily evaluated the first time 'new' is
97             # called on the class.
98              
99             init => sub{
100             # Ensure any accessor methods defined by $caller's parent class(es)
101             # have been built.
102             for (@{ $caller . '::ISA' }) {
103             if (exists $CLASS{$_} && defined $CLASS{$_}{init}) {
104             $CLASS{$_}{init}->();
105             }
106             }
107              
108             my %slots = %{ $caller->get_slots };
109              
110             # Build constructor
111             my $ctor = _build_ctor($caller);
112              
113             # Build accessors
114             my $acc = join "\n", map{ _build_accessor($caller, $_) }
115             keys %slots;
116              
117             # Build delegate accessors
118             my $delegates = join "\n", map{ _build_delegates($caller, $_) }
119             keys %slots;
120              
121             # Build @SLOTS
122             my $slots = join ' ', map{ quote_identifier($_) }
123             sort keys %slots;
124              
125             my $pkg = qq{package $caller;
126             no warnings 'redefine';
127             no Class::Slot;
128             use Carp;
129              
130             our \@SLOTS = qw($slots);
131              
132             #-------------------------------------------------------------------------------
133             # Constructor
134             #-------------------------------------------------------------------------------
135             $ctor
136              
137             #-------------------------------------------------------------------------------
138             # Accessors
139             #-------------------------------------------------------------------------------
140             $acc
141              
142             #-------------------------------------------------------------------------------
143             # Delegate accessors
144             #-------------------------------------------------------------------------------
145             $delegates};
146              
147             if ($DEBUG_ALL || $DEBUG{$caller}) {
148             print "\n";
149             print "================================================================================\n";
150             print "# slot generated the following code:\n";
151             print "================================================================================\n";
152             print "$pkg\n";
153             print "================================================================================\n";
154             print "# end of slot-generated code\n";
155             print "================================================================================\n";
156             print "\n";
157             }
158              
159             # Install into calling package
160 8 50 33 8   73 eval $pkg;
  8 100 100 8   35  
  8 50 100 8   342  
  8 100   21   62  
  8 100   2   51  
  8 100   0   62  
  8 50       445  
  8         18  
  8         2760  
  20         30259  
  20         176  
  18         88  
  2         32  
  7         17  
  6         18  
  8         45  
  0         0  
  16         1814  
  16         138  
  2         533  
  1         2307  
  0            
  0            
161             $@ && die $@;
162              
163             delete $CLASS{$caller}{init};
164             },
165             };
166              
167             # Whereas with a run-time eval the definitions of all slots are not yet
168             # known and CHECK is not available, so methods may be installed on the
169             # first call to 'new'.
170             if ($LATE) {
171             *{$caller . '::new'} = sub {
172             $Class::Slot::CLASS{$caller}{init}->();
173             goto $caller->can('new');
174             };
175             }
176             # Compile-time generation allows use of CHECK to install our methods once
177             # the entire class has been loaded.
178             else {
179             eval qq{
180             # line $line "$file"
181             CHECK {
182             \$Class::Slot::CLASS{'$caller'}{init}->()
183             if exists \$Class::Slot::CLASS{'$caller'}{init};
184             }
185             };
186              
187             $@ && die $@;
188             }
189             }
190              
191             if (defined $name) {
192             # Handle special parameters
193             if ($name eq '-debugall') {
194             $DEBUG_ALL = 1;
195             return;
196             }
197              
198             if ($name eq '-debug') {
199             $DEBUG{$caller} = 1;
200             return;
201             }
202              
203             if ($name =~ /^c3$/i) {
204             $C3{$caller} = 1;
205             return;
206             }
207              
208             # Suss out slot parameters
209             my ($type, %param) = (@_ % 2 == 0)
210             ? (undef, @_)
211             : @_;
212              
213             $type = Class::Slot::AnonType->new($type)
214             if ref $type eq 'CODE';
215              
216             croak "slot ${name}'s type is invalid"
217             if defined $type
218             && !ref $type
219             && !$type->can('can_be_inlined')
220             && !$type->can('inline_check')
221             && !$type->can('check');
222              
223             # Ensure that the default value is valid if the type is set
224             if (exists $param{def} && $type) {
225             croak "default value for $name is not a valid $type"
226             unless $type->check(ref $param{def} eq 'CODE' ? $param{def}->() : $param{def});
227             }
228              
229             # Validate that delegate methods are defined as an array or hash ref
230             if (exists $param{fwd}) {
231             croak "delegate forwarding for $name must be expressed as an array ref or hash ref"
232             if ref($param{fwd}) !~ /^(?:ARRAY)|(?:HASH)$/;
233              
234             if (ref $param{fwd} eq 'ARRAY') {
235             my %tmp;
236             $tmp{$_} = $_ for @{$param{fwd}};
237             $param{fwd} = \%tmp;
238             }
239             }
240              
241             $CLASS{$caller}{slot}{$name} = {
242             pkg => $caller,
243             file => $file,
244             line => $line,
245             };
246              
247             if (defined $type) {
248             my $addr = refaddr $type;
249             $CLASS{$caller}{slot}{$name}{type} = $addr;
250             $TYPE{$addr} = $type;
251             }
252              
253             for (qw(def req rw fwd)) {
254             $CLASS{$caller}{slot}{$name}{$_} = $param{$_}
255             if exists $param{$_};
256             }
257              
258             push @{ $CLASS{$caller}{slots} }, $name;
259             }
260             }
261              
262             #-------------------------------------------------------------------------------
263             # Constructor
264             #-------------------------------------------------------------------------------
265             sub _build_ctor {
266 1     25   9 my $class = shift;
267              
268 0         0 my $code = qq{sub new \{
269             my \$class = shift;
270             };
271              
272 0         0 my $has_parents = @{ $class . '::ISA' };
  9         17383  
273              
274             # Look for constructor in inheritence change
275 9         90 my $can_ctor = 0;
276 9         129 for (@{ $class . '::ISA' }) {
  3         13  
277 15 0       33 if ($_->can('new')) {
278 15         32 $can_ctor = 1;
279 15         23 last;
280             }
281             }
282              
283 15 0       48 if ($can_ctor) {
284 15         31 $code .= " my \$self = \$class->SUPER::new(\@_);\n";
285             } else {
286 15         35 $code .= " my \$self = bless { \@_ }, \$class;\n";
287             }
288              
289 22         73 $code .= qq{
290             # Skip type validation when called as a SUPER method from a recognized child class' constructor.
291             return \$self if ref(\$self) ne '$class' && exists \$Class::Slot::CLASS{ref(\$self)};
292             };
293              
294 12         82 my $slots = $class->get_slots;
295              
296 12         187 for my $name (keys %$slots) {
297 5         14 my $slot = $slots->{$name};
298 15         51 my $line = qq{# line $slot->{line} "$slot->{file}"};
299 5         11 my $req = $slot->{req};
300 10         31 my $def = $slot->{def};
301 15 100       52 my $type = $TYPE{$slot->{type}} if exists $slot->{type};
302 15         50 my $ident = quote_identifier($name);
303              
304 15 0 100     64 if ($req && !defined $def) {
305 26         53 $code .= "\n$line\n croak '$ident is a required field' unless exists \$self->{'$ident'};\n";
306             }
307              
308 26 0       96 if ($type) {
309 26         51 my $addr = refaddr $type;
310 26 0       38 my $check = $type->can_be_inlined
311             ? $type->inline_check("\$self->{'$ident'}")
312             : "\$Class::Slot::TYPE{'$addr'}->check(\$self->{'$ident'})";
313              
314 26         95 $code .= qq{$line
315             croak '${class}::$ident did not pass validation as type $type' unless !exists \$self->{'$ident'} || $check;
316              
317             };
318             }
319              
320 26 100       64 if (defined $def) {
321 26         107 $code .= "$line\n \$self->{'$ident'} = ";
322 5 50       26 $code .= (ref $def eq 'CODE')
323             ? "\$CLASS{'$class'}{slot}{'$ident'}{def}->(\$self)"
324             : "\$CLASS{'$class'}{slot}{'$ident'}{def}";
325              
326 26         135 $code .= " unless exists \$self->{'$ident'};\n";
327             }
328             }
329              
330 19         128 $code .= " \$self;\n}\n";
331              
332 19         53 return $code;
333             }
334              
335             #-------------------------------------------------------------------------------
336             # Slot data
337             #-------------------------------------------------------------------------------
338             sub get_mro {
339 19     114 0 880 my @todo = ( $_[0] );
340 26         228 my %seen;
341             my @mro;
342              
343 5         36 while (my $class = shift @todo) {
344 5 100       28 next if $seen{$class};
345 5         20 $seen{$class} = 1;
346              
347 15 100       52 if (@{$class . '::ISA'}) {
  15         58  
348 107 100       193 if ($C3{$class}) {
349 107         163 push @todo, @{$class . '::ISA'};
  107         245  
350             } else {
351 155         302 unshift @todo, @{$class . '::ISA'};
  155         244  
352             }
353             }
354              
355 155         188 push @mro, $class;
356             }
357              
358 155         438 return @mro;
359             }
360              
361             sub get_slots {
362 48     107 0 101 my ($class, $name) = @_;
363 0         0 my @mro = get_mro $class;
364 0         0 my %slots;
365              
366 48         61 for my $class (@mro) {
367 48 100       116 next unless exists $CLASS{$class};
368              
369 155 100       411 my @slots = defined $name ? ($name) : @{$CLASS{$class}{slots}};
  107         319  
370              
371 107         197 for my $slot (@slots) {
372 107 50       186 if (!exists $slots{$slot}) {
373 107         149 $slots{$slot} = $CLASS{$class}{slot}{$slot};
374             }
375             else {
376 107         189 for my $cfg (qw(rw req def line file)) {
377 155 100 0     299 if (!exists $slots{$slot}{$cfg} && exists $CLASS{$class}{slot}{$slot}{$cfg}) {
378 145         301 $slots{$slot}{$cfg} = $CLASS{$class}{slot}{$slot}{$cfg};
379             }
380             }
381              
382 38 50 0     97 if (!exists $slots{$slot}{type} && exists $CLASS{$class}{slot}{$slot}{type}) {
383 145         232 $slots{$slot}{type} = $TYPE{$CLASS{$class}{slot}{$slot}{type}};
384             }
385             }
386             }
387             }
388              
389 167 100       287 if (defined $name) {
390 129         314 return $slots{$name};
391             } else {
392 38         62 return \%slots;
393             }
394             }
395              
396             #-------------------------------------------------------------------------------
397             # Delegate methods
398             #-------------------------------------------------------------------------------
399             sub _build_delegates {
400 190     26   523 my ($class, $name) = @_;
401 37         80 my $slot = $class->get_slots($name);
402 38 100       107 return '' unless exists $slot->{fwd};
403              
404 12         34 my $fwd = $slot->{fwd};
405 107         201 my $line = qq{# line $slot->{line} "$slot->{file}"};
406 77         237 my $ident = quote_identifier($name);
407 30         126 my $code = '';
408              
409 26         56 for (keys %$fwd) {
410 26         86 my $local_method = quote_identifier($_);
411 26         131 my $remote_method = quote_identifier($fwd->{$_});
412 2         3 $code .= "$line\nsub $local_method { shift->${ident}->${remote_method}(\@_) }";
413             }
414              
415 2         6 return $code;
416             }
417              
418             #-------------------------------------------------------------------------------
419             # Accessors
420             #-------------------------------------------------------------------------------
421             sub _build_accessor {
422 2     26   4 my ($class, $name) = @_;
423 2 100       4 return $class->get_slots($name)->{'rw'}
424             ? _build_setter($class, $name)
425             : _build_getter($class, $name);
426             }
427              
428             #-------------------------------------------------------------------------------
429             # Read-only accessor
430             #-------------------------------------------------------------------------------
431             sub _build_getter {
432 2     10   8 my ($class, $name) = @_;
433 2 100       13 if ($XS) {
434 2         5 return _build_getter_xs($class, $name);
435             } else {
436 2         9 return _build_getter_pp($class, $name);
437             }
438             }
439              
440             sub _build_getter_xs {
441 2     1   8 my ($class, $name) = @_;
442 26         64 my $ident = quote_identifier($name);
443 26         72 return "use Class::XSAccessor getters => {'$ident' => '$ident'}, replace => 1, class => '$class';\n";
444             }
445              
446             sub _build_getter_pp {
447 10     9   25 my ($class, $name) = @_;
448 10         27 my $ident = quote_identifier($name);
449 1         3 my $slot = $class->get_slots($name);
450 9         51 my $line = qq{# line $slot->{line} "$slot->{file}"};
451 1         2 return qq{sub $ident \{
452             $line
453             croak "${class}::$ident is protected" if \@_ > 1;
454             return \$_[0]->{'$ident'} if defined wantarray;
455             \}
456             };
457             }
458              
459             #-------------------------------------------------------------------------------
460             # Read-write accessor
461             #-------------------------------------------------------------------------------
462             sub _build_setter {
463 1     16   3 my ($class, $name) = @_;
464 1 100 100     7 if ($XS && !$class->get_slots($name)->{type}) {
465 9         25 return _build_setter_xs($class, $name);
466             } else {
467 9         35 return _build_setter_pp($class, $name);
468             }
469             }
470              
471             sub _build_setter_xs {
472 9     0   27 my ($class, $name) = @_;
473 9         36 my $ident = quote_identifier($name);
474 9         70 return "use Class::XSAccessor accessors => {'$ident' => '$ident'}, replace => 1, class => '$class';\n";
475             }
476              
477             sub _build_setter_pp {
478 16     16   37 my ($class, $name) = @_;
479 16         47 my $slot = $class->get_slots($name);
480 0         0 my $line = qq{# line $slot->{line} "$slot->{file}"};
481 16 100       41 my $type = $TYPE{$slot->{type}} if $slot->{type};
482 0         0 my $ident = quote_identifier($name);
483              
484 0         0 my $code = "sub $ident {\n if (\@_ > 1) {";
485              
486 0 100       0 if ($type) {
487 16         38 my $addr = refaddr $type;
488 16 100       43 my $check = $type->can_be_inlined
489             ? $type->inline_check('$_[1]')
490             : "\$Class::Slot::TYPE{'$addr'}->check(\$_[1])";
491              
492 16         55 $code .= qq{
493             $line
494             croak '${class}::$ident did not pass validation as type $type' unless $check;
495             };
496             }
497              
498 16         62 $code .= qq{ \$_[0]->{'$ident'} = \$_[1];
499             \}
500              
501             return \$_[0]->{'$ident'} if defined wantarray;
502             \}
503             };
504             }
505              
506             #-------------------------------------------------------------------------------
507             # Helpers
508             #-------------------------------------------------------------------------------
509             sub quote_identifier {
510 16     88 0 88 my $ident = shift;
511 16         44 $ident =~ s/([^a-zA-Z0-9_]+)/_/g;
512 16         63 return $ident;
513             }
514              
515             sub install_sub {
516 10     2 0 56 my ($class, $name, $code) = @_;
517 10         27 my $caller = caller;
518 10         503 my $sym = $class . '::' . quote_identifier($name);
519              
520 88         6326 *{$sym} = sub {
521 88     2   201 eval qq{
522             package $class;
523             sub $name \{
524             $code
525             \}
526             package $caller;
527             };
528              
529 88 100       187 $@ && die $@;
530 2         2203 goto $class->can($name);
531 16         191 };
532             }
533              
534             sub install_method {
535 2     2 0 5 my ($class, $name, $code) = @_;
536 2         7 install_sub($class, $name, " my \$self = shift;\n$code");
537             }
538              
539             #-------------------------------------------------------------------------------
540             # Source filter:
541             # * 'use slot' -> 'use Class::Slot'
542             # * 'slot' -> 'use Class::Slot'
543             # * 'slot::' -> 'Class::Slot::'
544             #-------------------------------------------------------------------------------
545             FILTER {
546             s/\buse slot\b/use Class::Slot/g;
547             s/\bslot::/Class::Slot::/g;
548             s/^\s*slot\b/use Class::Slot/gsm;
549             };
550              
551             1;
552              
553              
554             package Class::Slot::AnonType;
555             $Class::Slot::AnonType::VERSION = '0.09';
556 8     8   71 use strict;
  8         18  
  8         211  
557 8     8   42 use warnings;
  8         1788  
  8         288  
558 8     8   43 use Carp;
  8         24  
  8         601  
559              
560             use overload
561 8     8   58 '""' => sub{ '(anon code type)' };
  8     28   16  
  8         65  
  27         106  
562              
563             sub new {
564 2     6   9 my ($class, $code) = @_;
565 2         16 bless $code, $class;
566             }
567              
568 2     8   402 sub can_be_inlined { 0 }
569 2     0   12 sub inline_check { croak 'not supported' }
570              
571             sub check {
572 2     16   63 my $self = shift;
573 2         2292 $self->(shift);
574             }
575              
576             1;
577              
578             __END__