File Coverage

blib/lib/Class/Contract.pm
Criterion Covered Total %
statement 343 565 60.7
branch 137 292 46.9
condition 49 108 45.3
subroutine 57 108 52.7
pod 3 30 10.0
total 589 1103 53.4


line stmt bran cond sub pod time code
1             package Class::Contract;
2 5     5   35182 use strict;
  5         11  
  5         201  
3 5     5   23 use vars qw( $VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS );
  5         7  
  5         357  
4             require Exporter;
5 5     5   23 use Carp;
  5         12  
  5         2133  
6            
7             $VERSION = '1.14';
8            
9             @ISA = qw(Exporter);
10             @EXPORT = qw(contract ctor dtor attr method pre impl post invar inherits
11             self value class abstract private optional check callstate
12             failmsg clon);
13             @EXPORT_OK = qw(scalar_attrs array_attrs hash_attrs methods old);
14             %EXPORT_TAGS = (DEFAULT => \@EXPORT,
15             EXTENDED => \@EXPORT_OK,
16             ALL => [@EXPORT, @EXPORT_OK]);
17            
18             my %contract;
19             my %data;
20             my %class_attr;
21             my $current;
22             my $msg_target;
23             my %no_opt; # NOT IN PRODUCTION
24             # $Class::Contract::hook = \%data; # for testing GC # NOT IN PRODUCTION
25            
26             my @class_dtors;
27 5     5   1424 END { $_->() foreach (@class_dtors) }
28            
29             my ($carp, $croak) = (
30             sub {
31             my (@c) = caller(0);
32             ($c[3] eq 'Class::Contract::__ANON__')
33             ? print STDERR (@_, " at $c[1] line $c[2]\n") : &carp
34             },
35             sub {
36             my (@c) = caller(0);
37             ($c[3] eq 'Class::Contract::__ANON__')
38             ? die(@_, " at $c[1] line $c[2]\n") : &croak
39             }
40             );
41            
42             sub import {
43 32     32   6169 my $class = $_[0];
44 32         72 my $caller = caller;
45 32 50       230 $contract{$caller}{use_old} = grep(/^old$/, @_) ? 1 : 0;
46 32         273 push @_, @EXPORT;
47 5     5   27 no strict 'refs';
  5         9  
  5         18256  
48             INIT {
49 5 50   5   37 *{$caller .'::croak'} = $croak if defined *{$caller .'::croak'}{'CODE'};
  0         0  
  5         56  
50 5 50       10 *{$caller .'::carp'} = $carp if defined *{$caller .'::carp'}{'CODE'};
  0         0  
  5         50  
51             }
52 32         18713 goto &Exporter::import;
53             }
54            
55             sub unimport {
56 0     0   0 my $class = shift;
57 0         0 my $caller = caller;
58 0 0       0 $contract{$caller}{use_old} = 0 if grep /^old$/, @_;
59             }
60            
61 27     27 0 542 sub contract(&) { $_[0]->(); _build_class(caller) }
  25         122  
62            
63             sub check(\%;$) {
64             # NOT IN PRODUCTION...
65 0 0   0 0 0 my $state = !$#_ ? 0 : $_[1] ? 1 : 0;
    0          
66 0 0       0 defined $_
    0          
67             or croak("Usage:\n\tcheck \%sentinel",
68             ($#_ ? " => $state" : ""),
69             " for ( \@classes );\n\n");
70            
71 0         0 my $forclass = $_;
72 0         0 $_[0]->{$forclass} =
73             bless { 'prev' => $no_opt{$forclass},
74             'forclass' => $forclass }, 'Class::Contract::FormerState';
75 0         0 $no_opt{$forclass} = $state;
76             # ...NOT IN PRODUCTION
77             }
78            
79             # NOT IN PRODUCTION...
80             sub Class::Contract::FormerState { # No function signature?
81 0     0 0 0 $no_opt{$_[0]->{'forclass'}} = $_[0]->{'prev'}; # my ($self) = @_;
82             }
83            
84             sub no_opt { # my ($class) = @_;
85 48 50   48 0 158 return exists $no_opt{$_[0]} ? $no_opt{$_[0]}
    50          
86             : exists $no_opt{'__ALL__'} ? $no_opt{'__ALL__'}
87             : 0;
88             }
89             # ...NOT IN PRODUCTION
90            
91             sub _location { # scalar context returns file and line of external code
92             # array context returns package aka 'owner', file and line
93 312     312   383 my ($i, @c, $owner);
94 312         2172 while (@c = (caller($i++))[0..2]) {
95 923 100       6088 if ($c[0] !~ /^Class::Contract$/) {
96 312 50       616 $owner = $c[0] if !$owner;
97 312 50       1000 if ($c[1] !~ /^\(eval \d+\)$/) {
98 312 50       1648 return (wantarray ? $owner : (), join ' line ', @c[1,2]);
99             }
100             }
101             }
102             }
103            
104             my %def_type = (
105             'attr' => 'SCALAR',
106             'method' => '',
107             'ctor' => '',
108             'dtor' => '',
109             'clon' => '',
110             );
111            
112             sub _member {
113 120     120   211 my ($kind, $name, $type) = @_;
114 120         207 my ($owner, $location) = _location;
115 120 100       258 $name = '' unless $name;
116            
117 120 50       426 if (defined $contract{$owner}{$kind}{$name}) {
118 0 0       0 croak "\u$kind ${owner}::$name redefined" if $name;
119 0         0 croak "Unnamed $kind redefined";
120             }
121            
122 120   100     2138 $contract{$owner}{$kind}{$name} = $current =
      100        
123             bless {'name' => $name,
124             'type' => $type || $def_type{$kind},
125             'gentype' => $type || $def_type{$kind}, # NOT IN PRODUCTION
126             'loc' => $location,
127             'shared' => 0,
128             'private' => 0,
129             'abstract' => 0,
130             'pre' => [], # NOT IN PRODUCTION
131             'post' => [], # NOT IN PRODUCTION
132             }, "Class::Contract::$kind";
133            
134             # NOT IN PRODUCTION...
135 120 100       415 $current->{'gentype'} = 'OBJECT'
136             unless $current->{'gentype'} =~ /\A(SCALAR|ARRAY|HASH)\z/;
137             # ...NOT IN PRODUCTION
138 120         733 return $current;
139             }
140            
141 6     6 0 35 sub attr($;$) { _member('attr' => @_) }
142 98     98 1 6972 sub method($) { _member('method' => @_) }
143 10     10 0 51 sub ctor(;$) { _member('ctor' => @_) }
144 6     6 0 26 sub dtor() { _member('dtor') }
145 0     0 0 0 sub clon() { _member('clone') }
146            
147 0     0 0 0 sub scalar_attrs(@) { map _member('attr', $_, 'SCALAR'), @_ }
148 0     0 0 0 sub array_attrs(@) { map _member('attr', $_, 'ARRAY'), @_ }
149 0     0 0 0 sub hash_attrs(@) { map _member('attr', $_, 'HASH'), @_ }
150 0     0 1 0 sub methods(@) { map _member('attr', $_), @_ }
151            
152 96     96 1 332 sub class(@) { $_->{'shared'} = 1 foreach(@_); @_ }
  96         1654  
153 4     4 0 16 sub abstract(@) { $_->{'abstract'} = 1 foreach(@_); @_ }
  4         37  
154 1     1 0 3 sub private(@) { $_->{'private'} = 1 foreach(@_); @_ }
  1         16  
155            
156             my %def_msg = (
157             'pre' => 'Pre-condition at %s failed',
158             'post' => 'Post-condition at %s failed',
159             'invar' => 'Class invariant at %s failed',
160             'impl' => undef
161             );
162            
163             sub _current {
164 180     180   261 my ($field, $code) = @_;
165 180 50       367 croak "Unattached $field" unless defined $current;
166 180 100 66     1456 croak "Attribute cannot have implementation"
167             if $current->isa('Class::Contract::attr') && $field eq 'impl';
168            
169 179         891 my $descriptor = bless {
170             'code' => $code,
171             'opt' => 0, # NOT IN PRODUCTION
172             'msg' => $def_msg{$field},
173             }, 'Class::Contract::current';
174 179         334 @{$descriptor}{qw(owner loc)} = _location;
  179         560  
175            
176 179 100 66     2242 if ($field eq 'impl' && !( $current->isa('Class::Contract::ctor')
      100        
177             || $current->isa('Class::Contract::dtor')
178             || $current->isa('Class::Contract::clone') )) {
179 93         188 $current->{$field} = $descriptor
180             } else {
181 86         104 push @{$current->{$field}}, $descriptor
  86         210  
182             }
183            
184 179         3296 $msg_target = $descriptor;
185             }
186            
187             sub failmsg {
188 0 0   0 0 0 croak "Unattached failmsg" unless $msg_target;
189 0         0 $msg_target->{'msg'} = shift;
190             }
191            
192 62     62 0 150 sub pre(&) { _current('pre' => @_) }
193 8     8 0 27 sub post(&) { _current('post' => @_) }
194 110     110 0 339 sub impl(&) { _current('impl' => @_) }
195            
196             sub optional { # my (@descriptors) = @_;
197 0     0 0 0 $_->{'opt'} = 1 foreach(@_); @_ # NOT IN PRODUCTION
198 0         0 }
199            
200             sub invar(&) {
201 0     0 0 0 my ($code) = @_;
202            
203 0         0 my $descriptor = {
204             'code' => $code,
205             'opt' => 0, # NOT IN PRODUCTION
206             'msg' => $def_msg{'invar'},
207             };
208 0         0 @{$descriptor}{qw(owner loc)} = _location;
  0         0  
209            
210 0         0 push @{$contract{$descriptor->{'owner'}}{'invar'}}, $descriptor;
  0         0  
211 0         0 $msg_target = $descriptor;
212             }
213            
214            
215             sub inherits(@) {
216 13     13 0 64 my ($owner) = _location;
217 13         41 foreach (@_) {
218 17 100       419 croak "Can't create circular reference in inheritence\n$_ is a(n) $owner"
219             if $_->isa($owner)
220             }
221 12         20 push @{$contract{$owner}{'parents'}}, @_;
  12         192  
222             }
223            
224             sub _build_class($) {
225 25     25   44 my ($class) = @_;
226 25         49 my $spec = $contract{$class};
227 25         70 _inheritance($class, $spec);
228 24         73 _attributes($class, $spec);
229 24         69 _methods($class, $spec);
230 23         67 _constructors($class, $spec);
231 23         68 _destructors($class, $spec);
232 23         70 _clones($class, $spec);
233 23         387 1;
234             }
235            
236             localscope: {
237             my @context;
238             my %clear; # NOT IN PRODUCTION;
239             sub _set_context {
240 52     52   180 push @context, {'__SELF__' => shift};
241            
242             # NOT IN PRODUCTION...
243 52         105 my $proto = $context[-1]{__SELF__};
244 52 100       126 my ($class, $obj) = ref($proto)
245             ? (ref($proto), $proto)
246             : ($proto, undef);
247 52 50       122 return if $class =~ /^Class::Contract::Old::_/;
248            
249 52 50       165 if ($contract{$class}{'use_old'}) {
250 0         0 my $class_old = "Class::Contract::Old::_$#context";
251 0         0 _pkg_copy($class, $class_old);
252 0         0 my $old = $class_old;
253 0 0       0 if ($obj) {
254             # Like generic_clone but into the cloned class
255 0         0 my $old_key = \ my $undef;
256 0         0 $old = bless \ $old_key, $class_old;
257 0 0       0 $data{$$old} = _dcopy($data{$$obj}) if exists $data{$$obj};
258             }
259 0         0 $context[-1]{__OLD__} = $old;
260             }
261             # ...NOT IN PRODUCTION
262             }
263             sub _free_context {
264             return pop @context
265 39     39   53 }
266             sub old() {
267 0 0   0 0 0 croak "No context. Can't call &old" unless @context;
268 0         0 my $self = $context[-1]{__SELF__};
269 0   0     0 my $class = ref($self) || $self;
270 0 0       0 croak "Support for &old has been toggled off"
271             unless ($contract{$class}{'use_old'});
272 0         0 $context[-1]{__OLD__} # NOT IN PRODUCTION
273             }
274            
275             my @value;
276 36     36   183 sub _set_value { push @value, \@_ }
277 36 50   36   46 sub _free_value { my $v = pop @value; wantarray ? @$v : $v->[0] }
  36         615  
278            
279             sub value {
280 0 0   0 0 0 croak "Can't call &value " unless @value;
281 0         0 return $value[-1];
282             }
283            
284             sub self() {
285 324 50   324 0 676 if (@_) {
286             # NOT IN PRODUCTION...
287 0   0     0 croak "Usage:\tself(\$class_or_object)"
288 0 0       0 unless defined *{join(ref($_[0])||$_[0], '::')};
289             # ...NOT IN PRODUCTION
290 0         0 $context[-1]{__SELF__} = shift;
291             }
292 324 50       563 croak "No context. Can't call &self" unless @context;
293 324         1284 $context[-1]{__SELF__}
294             }
295            
296             sub callstate() {
297 0 0   0 0 0 croak "No context. Can't call &callstate" unless @context;
298 0         0 return $context[-1];
299             }
300             }
301            
302             sub _inheritance { # A D Invokation order
303             # Inheritence is left-most depth-first. Destructors # /\ |
304             # are called in reversed order as the constructors # B C E ctor: ABCDEF
305             # Diamond patterns in inheritence are 'handled' by # \// dtor: FEDCBA
306             # looking for and skipping duplicate anonymous refs # F
307            
308 25     25   38 my ($classname, $spec) = @_;
309 25         30 my (%inherited_clause, %inherited_impl);
310 25 100       33 foreach my $ancestor ( reverse @{$spec->{'parents'} || [] } ) {
  25         143  
311 16   50     55 my $parent = $contract{$ancestor} || next;
312 16 50 33     53 if ($parent->{'use_old'} and not $spec->{'use_old'}) {
313 0         0 croak("Derived class $classname, has not toggled on support for ->old\n",
314             "which is required by ancestor $ancestor. Did you forget to\n",
315             "declare: use Class::Contract 'old'; ?");
316             }
317 16         39 foreach my $clause ( qw( attr method ctor clone dtor ) ) {
318 77 50       78 foreach my $name ( keys %{ $parent->{$clause} || {} } ) {
  77         323  
319             # Inherit each clause from ancestor unless defined
320 100 50 66     300 if (! defined $spec->{$clause}{$name}
321             and not exists $inherited_clause{$name}) {
322 22         96 $inherited_clause{$name}++;
323 22         24 %{$spec->{$clause}{$name}} = (%{$parent->{$clause}{$name}});
  22         122  
  22         104  
324 22         62 $spec->{$clause}{$name}{'pre'} = []; # NOT IN PRODUCTION
325 22         76 next;
326             }
327            
328             # Inherit ctor/clone/dtor invokation from ancestors
329 78 100       226 if ($clause =~ /^(ctor|clone|dtor)$/) {
330 20 100 66     78 if (defined $parent->{$clause}{$name}{'impl'}
  14         62  
331             and @{$parent->{$clause}{$name}{'impl'}}) {
332 14         16 my (@impl, %seen) = (@{$spec->{$clause}{$name}{'impl'}});
  14         44  
333 14 50       40 if (@impl) {
334 14         138 $seen{$impl[$_]} = $_ foreach (0..$#impl);
335 14         22 foreach my $item ( @{$parent->{$clause}{$name}{'impl'}} ) {
  14         36  
336 21 100       70 splice(@{$spec->{$clause}{$name}{'impl'}}, $seen{$item}, 1)
  2         10  
337             if exists $seen{$item};
338             }
339             }
340 8         17 $clause ne 'dtor'
341 8         30 ? unshift(@{$spec->{$clause}{$name}{'impl'}},
342 6         14 @{$parent->{$clause}{$name}{'impl'}})
343 6         23 : push(@{$spec->{$clause}{$name}{'impl'}},
344 14 100       39 @{$parent->{$clause}{$name}{'impl'}});
345             }
346             }
347            
348             # Get implementation from ancestor if derived but not redefined
349 78 100       151 if ($clause eq 'method') {
350 58 50 33     274 if (! defined $spec->{$clause}{$name}{'impl'}
351             or $inherited_impl{$name}) {
352 0         0 $inherited_impl{$name}++;
353 0         0 $spec->{$clause}{$name}{'impl'}=$parent->{$clause}{$name}{'impl'};
354             }
355 58 100 66     430 croak("Forget 'private'? $classname inherits private $name from ",
356             "$ancestor\n")
357             if ($parent->{$clause}{$name}{'private'}
358             and not $spec->{$clause}{$name}{'private'})
359             }
360             # NOT IN PRODUCTION...
361             # Inherit all post-conditions from ancestors
362 77 100       86 if (@{$parent->{$clause}{$name}{'post'}||[]}) {
  77 100       355  
363 2         3 my (@post, %seen) = (@{$spec->{$clause}{$name}{'post'}});
  2         8  
364 2 50       14 if (@post) {
365 2         15 $seen{$post[$_]} = $_ foreach (0..$#post);
366 2         4 foreach my $item ( @{$parent->{$clause}{$name}{'post'}} ) {
  2         8  
367 3 50       12 splice(@{$spec->{$clause}{$name}{'post'}}, $seen{$item}, 1)
  0         0  
368             if exists $seen{$item};
369             }
370             }
371 2         6 push(@{$spec->{$clause}{$name}{'post'}},
  2         7  
372 2         4 @{$parent->{$clause}{$name}{'post'}});
373             }
374             # ...NOT IN PRODUCTION
375             }
376             }
377             # NOT IN PRODUCTION...
378             # Inherit all class invariants from ancestors
379 15 50 33     74 if (defined $parent->{'invar'} and @{$parent->{'invar'}}) {
  0         0  
380 0 0       0 defined $spec->{'invar'} or $spec->{'invar'} = [];
381 0         0 my (@invar, %seen) = (@{$spec->{'invar'}});
  0         0  
382 0 0       0 if (@invar) {
383 0         0 $seen{$invar[$_]} = $_ foreach (0..$#invar);
384 0         0 foreach (@{$parent->{'invar'}}) {
  0         0  
385 0 0       0 splice(@{$spec->{'invar'}}, $seen{$_}, 1) if exists $seen{$_}
  0         0  
386             }
387             }
388 0         0 push @{$spec->{'invar'}}, @{$parent->{'invar'}};
  0         0  
  0         0  
389             }
390             # ...NOT IN PRODUCTION
391             }
392            
393 5     5   47 no strict 'refs';
  5         10  
  5         1580  
394 24 100       42 unshift @{"${classname}::ISA"}, @{ $spec->{'parents'} || [] };
  24         174  
  24         291  
395             }
396            
397             sub _attributes {
398 24     24   44 my ($classname, $spec) = @_;
399            
400 24         49 while ( my ($name, $attr) = each %{$spec->{'attr'}} ) {
  32         174  
401 8 100       25 if ($attr->{'shared'}) {
402             my $ref = $class_attr{$classname}{$name} =
403             $attr->{'type'} eq 'ARRAY' ? []
404             : $attr->{'type'} eq 'HASH' ? {}
405 3         11 : $attr->{'type'} eq 'SCALAR' ? do { \ my $scalar }
406 3 50 0     24 : eval { $attr->{'type'}->new }
    50          
    50          
407             || croak "Unable to create $attr->{'type'} object ",
408             "for class attribute $name";
409             }
410            
411             localscope: {
412 5     5   30 no strict 'refs';
  5         10  
  5         2960  
  8         11  
413 8         40 local $^W;
414 8         57 *{"${classname}::$name"} = sub {
415 3 100 66 3   164 croak(qq|Can\'t access object attr w/ class reference |,$attr->{'loc'})
416             unless ($attr->{'shared'} or ref($_[0]));
417            
418 2         4 my $caller = caller;
419 2 100       247 croak "attribute ${classname}::$name inaccessible from package $caller"
420             unless $classname->isa($caller);
421            
422 1         3 my $self = shift;
423 1 50 33     14 _set_context(($attr->{'shared'} ? ref($self)||$self : $self),
424             join ' line ', [caller]->[1,2]);
425 1 50       5 my $attr_ref = ($attr->{'shared'})
426             ? $class_attr{$classname}{$name}
427             : $data{$$self}{$name};
428 1         4 _set_value $attr_ref;
429            
430             # NOT IN PRODUCTION...
431 1         4 my @fail = generic_check('pre', 'attr' => $name, $spec);
432 1 50       4 croak @fail if @fail;
433             # ...NOT IN PRODUCTION
434            
435 1         3 _free_context;
436            
437             # NOT IN PRODUCTION...
438 1         5 return "Class::Contract::Post$attr->{'gentype'}"->new(
439             $attr->{'post'}, $attr_ref, $name,
440 1 50       2 ) if @{$attr->{'post'}};
441             # ...NOT IN PRODUCTION
442            
443 1         3 scalar _free_value;
444 1         16 return $attr_ref;
445 8         75 };
446             }
447             }
448             }
449            
450             sub _methods {
451 24     24   40 my ($classname, $spec) = @_;
452            
453 24         34 while ( my ($name, $method) = each %{$spec->{'method'}} ) {
  123         455  
454 100   100     334 $spec->{'abstract'} ||= $method->{'abstract'};
455 100 100       236 unless ($method->{'impl'}) {
456 5 100       12 if ($method->{'abstract'}) {
457             $method->{'impl'} = {'code' => sub {
458 1     1   277 croak "Can't call abstract method ${classname}::$name"
459             } }
460 4         21 } else {
461 1         174 croak qq{No implementation for method $name at $method->{'loc'}.\n},
462             qq{(Did you forget to declare it 'abstract'?)\n}
463             }
464             }
465            
466             local_scope: {
467 99         87 local $^W;
  99         185  
468 5     5   60 no strict 'refs';
  5         11  
  5         10516  
469 99         563 *{"${classname}::$name"} = sub {
470 48     48   5735 my $caller = caller;
471 48 50 33     189 croak("private method ${classname}::$name inaccessible from ",
472             scalar caller)
473             if ($method->{'private'}
474             and not ($classname->isa($caller))); # or $caller->isa($classname)));
475            
476 48         79 my $self = shift;
477 48 50 33     447 _set_context(($method->{'shared'} ? ref($self)||$self : $self),
478             join ' line ', [caller]->[1,2]);
479            
480             # NOT IN PRODUCTION...
481 48 50 33     183 croak(qq|Can\'t invoke object method w/ class name |, $method->{'loc'})
482             unless ($method->{'shared'} or ref($self));
483            
484 48         100 my $no_opt = no_opt($classname);
485 48         124 my @fail = generic_check('pre', 'method' => $name, $spec, @_);
486 48 100       1736 croak @fail if @fail;
487             # ...NOT IN PRODUCTION
488            
489 37 50       632 _set_value wantarray
490             ? $method->{'impl'}{'code'}->(@_)
491             : scalar $method->{'impl'}{'code'}->(@_);
492            
493             # NOT IN PRODUCTION...
494 35         74 generic_check('post', 'method' => $name, $spec, @_);
495 35 50       116 generic_check('invar', 'method' => $name, $spec, @_)
496             if (caller ne $classname);
497             # ...NOT IN PRODUCTION
498            
499 35         55 _free_context;
500 35         77 _free_value;
501 99         642 };
502             }
503             }
504             }
505            
506             # NOT IN PRODUCTION...
507             sub generic_check {
508 160 50 66 160 0 262 return if (ref(self)||self) =~ /^Class::Contract::Old::_/;
509            
510 160         338 my ($type, $kind, $name, $class_spec, @args) = @_;
511 160 100       174 my @specs = @{$class_spec->{$kind}{$name}{$type}||[]};
  160         5320  
512 160         192 my @errors;
513            
514 160         242 foreach my $spec ( @specs ) {
515 70 100 33     1375 next if $spec->{'opt'} && no_opt($spec->{'owner'})
      66        
516             || $spec->{'code'}->(@args);
517 29         318 push @errors, sprintf($spec->{'msg'},$spec->{'loc'})."\n";
518             }
519            
520 160 50       711 @errors ? croak @errors : return unless $type eq 'pre';
    100          
521 84 100 100     345 return if @specs && !@errors;
522            
523             # OTHERWISE SATISFY AT LEAST ONE PARENT?
524 53 100       64 foreach my $ancestor ( @{$class_spec->{'parents'}||[]} ) {
  53         180  
525 48         87 my $parent = $contract{$ancestor};
526 48 50       127 next unless exists $parent->{$kind}{$name};
527 48         54 my $has_pre = scalar @{$parent->{$kind}{$name}{'pre'}};
  48         130  
528 48 100       97 unless ($has_pre) {
529 20 100       22 foreach my $p (@{$parent->{'parents'}||[]}) {
  20         74  
530 15 100 50     177 $has_pre++ and last if _hasa($p, $kind, $name, 'pre');
531             }
532             }
533            
534 48 100       103 if ($has_pre) {
535 32         71 my @par_err = generic_check($type, $kind, $name, $parent, @args);
536 32 100       94 return unless @par_err;
537 12         45 push @errors, @par_err;
538             }
539             }
540 33         104 return @errors;
541             }
542            
543             sub _hasa {
544 15     15   30 my ($class, $kind, $name, $type) = (@_);
545 15 50       56 return 0 unless defined $contract{$class}{$kind}{$name};
546            
547 15 100       19 my $has = @{$contract{$class}{$kind}{$name}{$type} || []} ? 1 : 0;
  15 100       79  
548 15 100       32 unless ($has) {
549 11 50       14 foreach my $ancestor (@{$contract{$class}{'parents'} || []}) {
  11         59  
550 0 0 0     0 $has++ and last if _hasa($ancestor, $kind, $name, $type);
551             }
552             }
553 15         64 return $has;
554             }
555             # ...NOT IN PRODUCTION
556            
557             sub generic_ctor {
558 3     3 0 7 my ($class) = @_;
559            
560 3 100       219 croak "Class $class has abstract methods. Can't create $class object"
561             if $contract{$class}{'abstract'};
562            
563 2         4 my $key = \ my $undef;
564 2         4 my $obj = \ $key;
565 2         4 bless $obj, $class;
566            
567 2         5 my $attr = $contract{$class}{'attr'};
568            
569 2         7 foreach my $attrname ( keys %$attr ) {
570 1 50 33     9 unless ($attr->{$attrname} && $attr->{$attrname}{'shared'}) {
571             my $ref = $data{$key}{$attrname}
572             = $attr->{$attrname}{'type'} eq 'ARRAY' ? []
573             : $attr->{$attrname}{'type'} eq 'HASH' ? {}
574 1         6 : $attr->{$attrname}{'type'} eq 'SCALAR' ? do { \my $scalar }
575 1 50 0     16 : eval { $attr->{$attrname}{type}->new }
    50          
    50          
576             || croak "Unable to create $attr->{$attrname}{'type'} ",
577             "object for attribute $attrname";
578             }
579             }
580            
581 2         6 return $obj;
582             }
583            
584             sub generic_clone ($) {
585 0     0 0 0 my $self = shift;
586 0         0 my $ref = ref($self);
587 0 0 0     0 croak "usage: \$object->clone -Invalid arg $self"
588             unless ($ref and
589             $ref !~ /^(HASH|ARRAY|SCALAR|GLOB|FORMAT|CODE|Regexp|REF)$/);
590 0         0 my $key = \ my $undef;
591 0         0 my $obj = bless \$key, $ref;
592 0 0       0 $data{$key} = _dcopy($data{$$self}) if exists $data{$$self};
593 0         0 return $obj;
594             }
595            
596            
597             sub _constructors {
598 23     23   39 my ($classname, $spec) = @_;
599 23         34 my $noctor = 1;
600            
601 23         33 while ( my ($name, $ctor) = each %{$spec->{'ctor'}} ) {
  38         148  
602 15   33     76 $noctor &&= $ctor->{'shared'}
603             }
604            
605 23 100       138 $spec->{'ctor'}{'new'} = bless {
606             'name' => 'new',
607             'shared' => 0,
608             'abstract' => 0,
609             'loc' => ''
610             }, 'Class::Contract::ctor'
611             if $noctor;
612            
613 23         39 while ( my ($name, $ctor) = each %{$spec->{'ctor'}} ) {
  46         209  
614 23   66     102 $spec->{'abstract'} ||= $ctor->{'abstract'};
615            
616 23 50       53 if ($ctor->{'shared'}) {
617 0         0 localscope: {
618 0         0 local $^W;
619 5     5   44 no strict 'refs';
  5         10  
  5         1085  
620             my $classctor = sub {
621 0     0   0 my $self = shift;
622 0   0     0 _set_context ref($self)||$self;
623            
624             # NOT IN PRODUCTION...
625 0         0 my @fail = generic_check('pre', 'ctor' => $name, $spec, @_);
626 0 0       0 croak @fail if @fail;
627             # ...NOT IN PRODUCTION
628            
629 0         0 $_->{'code'}->(@_) foreach ( @{$ctor->{'impl'}} );
  0         0  
630            
631             # NOT IN PRODUCTION...
632 0         0 generic_check('post', 'ctor' => $name, $spec, @_);
633 0 0       0 generic_check('invar','ctor' => $name, $spec, @_)
634             if (caller ne $classname);
635             # ...NOT IN PRODUCTION
636            
637 0         0 _free_context;
638 0         0 };
639 0         0 $classname->$classctor();
640             # *{"${classname}::$name"} = $classctor if $name;
641             }
642             } else {
643 23         57 localscope:{
644 23         26 local $^W;
645 5     5   26 no strict 'refs';
  5         11  
  5         1429  
646 23         154 *{"${classname}::$name"} = sub {
647 3     3   22 my $proto = shift;
648 3   33     26 my $class = ref($proto)||$proto;
649 3         10 my $self = Class::Contract::generic_ctor($class);
650 2         14 _set_context $self;
651            
652             # NOT IN PRODUCTION...
653 2         10 my @fail = generic_check('pre', 'ctor' => $name, $spec, @_);
654 2 50       7 croak @fail if @fail;
655             # ...NOT IN PRODUCTION
656            
657 2         4 $_->{'code'}->(@_) foreach ( @{$ctor->{'impl'}} );
  2         25  
658            
659             # NOT IN PRODUCTION...
660 2         86 generic_check('post', 'ctor' => $name, $spec, @_);
661 2 50       12 generic_check('invar','ctor' => $name, $spec, @_)
662             if (caller ne $classname);
663             # ...NOT IN PRODUCTION
664            
665 2         6 _free_context;
666 2         30 return $self;
667             }
668 23         149 }
669             }
670             }
671             }
672            
673 5     5   6538 use Data::Dumper;
  5         102996  
  5         815  
674             sub _destructors {
675            
676 23     23   36 my ($classname, $spec) = @_;
677 23         32 my $dtorcount = 0;
678            
679 23         49 while ( my ($name, $dtor) = each %{$spec->{'dtor'}} ) {
  29         124  
680 6   33     59 $spec->{'abstract'} ||= $dtor->{'abstract'};
681            
682 6 50       12 if ($dtor->{'shared'}) {
683 0         0 localscope: {
684 0         0 local $^W;
685 5     5   50 no strict 'refs';
  5         11  
  5         1560  
686             my $classdtor = sub {
687 0 0   0   0 croak "Illegal explicit invokation of class dtor", $dtor->{'loc'}
688             if caller() ne 'Class::Contract';
689 0         0 my $self = shift;
690 0 0       0 $self = ref $self if ref $self;
691            
692 0         0 _set_context $self;
693            
694             # NOT IN PRODUCTION...
695 0         0 my @fail = generic_check('pre', 'dtor' => $name, $spec, @_);
696 0 0       0 croak @fail if @fail;
697             # ...NOT IN PRODUCTION
698            
699 0         0 $_->{'code'}->(@_) foreach ( @{$dtor->{'impl'}} );
  0         0  
700            
701 0         0 generic_check('post', 'dtor' => $name, $spec, @_);# NOT IN PRODUCTION
702 0         0 _free_context;
703 0         0 };
704            
705 0     0   0 push @class_dtors, sub { $classname->$classdtor() };
  0         0  
706             }
707             } else {
708 6 50       14 croak "Class $classname has too many destructors" if $dtorcount++;
709            
710 6         13 localscope: {
711 6         6 local $^W;
712 5     5   30 no strict 'refs';
  5         9  
  5         2032  
713             my $objdtor = sub {
714 1 50   1   4 croak "Illegal explicit invokation of object dtor", $dtor->{'loc'}
715             if caller() ne 'Class::Contract';
716            
717 1         2 my $self = shift;
718 1         3 _set_context $self;
719            
720             # NOT IN PRODUCTION...
721 1         3 my @fail = generic_check('pre', 'dtor' => $name, $spec, @_);
722 1 50       4 croak @fail if @fail;
723             # ...NOT IN PRODUCTION
724            
725 1 50       2 $_->{'code'}->(@_) foreach ( @{$dtor->{'impl'}||[]} );
  1         19  
726            
727             # NOT IN PRODUCTION...
728 1         6 generic_check('post', 'dtor' => $name, $spec, @{[@_]});
  1         4  
729 1 50       5 generic_check('invar', 'dtor' => $name, $spec, @{[@_]})
  1         3  
730             if (caller ne $classname);
731             # ...NOT IN PRODUCTION
732            
733 1         3 _free_context;
734 1         3 return;
735 6         44 };
736            
737 6         38 *{"${classname}::DESTROY"} = sub {
738 1     1   8 $_[0]->$objdtor();
739 1 50       1 delete $data{${$_[0]}} if exists $data{${$_[0]}};
  1         25  
  1         14  
740 6         18 };
741             }
742             }
743             }
744 23 100       41 unless (defined &{"${classname}::DESTROY"}) {
  23         141  
745 17         39 local $^W;
746 5     5   30 no strict 'refs';
  5         14  
  5         1126  
747 17         97 *{"${classname}::DESTROY"} = sub {
748 1 50   1   11 delete $data{${$_[0]}} if exists $data{${$_[0]}};
  0         0  
  1         24  
749 17         68 };
750             }
751             }
752            
753             sub _clones {
754 23     23   46 my ($classname, $spec) = @_;
755 23         38 my $clone_count = 0;
756            
757 23 100       157 $spec->{'clone'}{''} = bless {
758             'name' => '',
759             'shared' => 0,
760             'abstract' => 0,
761             'loc' => ''
762             }, 'Class::Contract::clone'
763             unless $spec->{'clone'};
764            
765 23         38 while ( my ($name, $clause) = each %{$spec->{'clone'}} ) {
  46         210  
766            
767 23   66     104 $spec->{'abstract'} ||= $clause->{'abstract'};
768 23 50       60 croak "'class' clause can not be used to qualify 'clon'"
769             if $clause->{'shared'};
770 23 50       54 croak "too many clon clauses" if $clone_count++;
771            
772 23         56 localscope: {
773 23         30 local $^W;
774 5     5   40 no strict 'refs';
  5         10  
  5         4672  
775 23         182 *{"${classname}::clone"} = sub {
776 0     0     my $self = shift;
777 0           $self = generic_clone($self);
778 0           _set_context $self;
779            
780             # NOT IN PRODUCTION...
781 0           my @fail = generic_check('pre', 'dtor' => $name, $spec, @_);
782 0 0         croak @fail if @fail;
783             # ...NOT IN PRODUCTION
784            
785 0 0         $_->{'code'}->(@_) foreach ( @{$clause->{'impl'}||[]} );
  0            
786            
787             # NOT IN PRODUCTION...
788 0           generic_check('post', $clause => $name, $spec, @{[@_]});
  0            
789 0 0         generic_check('invar', $clause => $name, $spec, @{[@_]})
  0            
790             if (caller ne $classname);
791             # ...NOT IN PRODUCTION
792            
793 0           _free_context;
794 0           return $self;
795 23         132 };
796             }
797             }
798             }
799            
800             localscope: {
801             my ($a,$z) = (qr/(^|^.*?=)/, qr/\(.*?\)$/);
802             my %seen = ();
803             my $depth = 0;
804             sub _dcopy { # Dereference and return a deep copy of whatever's passed
805 0     0     my ($r, $ref, $rval);
806 0 0         $ref = ref($_[0]) or return $_[0];
807 0 0         exists $seen{$_[0]} and return $seen{$_[0]};
808 0           $depth++;
809            
810 0           $r =
811 0           ($_[0] =~ /${a}HASH$z/) ? {map _dcopy($_), (%{$_[0]})}
812             : ($_[0] =~ /${a}ARRAY$z/) ? [map _dcopy($_), @{$_[0]} ]
813 0 0         : ($_[0] =~ /${a}SCALAR$z/) ? do { my $v = _dcopy(${$_[0]}); \$v }
  0 0          
  0 0          
  0 0          
    0          
    0          
    0          
    0          
    0          
814             : ($_[0] =~ /${a}FORMAT$z/) ? $_[0]
815             : ($_[0] =~ /${a}CODE$z/) ? $_[0]
816             : ($_[0] =~ /${a}Regexp$z/) ? $_[0]
817             : ($_[0] =~ /${a}REF$z/) ? $_[0]
818             : ($_[0] =~ /${a}GLOB$z/) ? $_[0]
819             : $_[0]->can('clone') ? $_[0]->clone : $_[0];
820            
821 0 0         $rval = $ref =~ /^(HASH|ARRAY|SCALAR|GLOB|FORMAT|CODE|Regexp|REF)$/
822             ? $r
823             : bless $r, $ref;
824            
825 0 0 0       --$depth
826             and $seen{$_[0]} = $rval
827             or %seen = ();
828            
829 0           return $rval;
830             }
831             }
832            
833             # NOT IN PRODUCTION...
834             sub _pkg_copy ($$) { # $from_package, $to_package
835 5     5   36 no strict 'refs';
  5         11  
  5         1415  
836 0 0   0     defined *{$_[0] . '::'}
  0            
837             or croak "_pkg_copy() Can't clone from non-existant package $_[0]";
838 0 0         defined *{$_[1] . '::'} and *{$_[1] . '::'} = {};
  0            
  0            
839            
840 0           foreach my $glob (values %{*{$_[0] . '::'}}) {
  0            
  0            
841 0 0         my ($varname) = $glob =~ /^\*$_[0]::(.*)/ or next;
842 0           foreach my $slot (qw(SCALAR ARRAY HASH CODE FORMAT)) {
843 0           my $ref = _dcopy(*{"$_[0]::$varname"}{$slot});
  0            
844 0 0         *{"$_[1]::$varname"} = $ref if defined $ref;
  0            
845             }
846             }
847             }
848            
849             sub _pkg_clear ($) {
850 5     5   37 no strict 'refs';
  5         9  
  5         10171  
851 0     0     my ($package) = shift;
852 0           my $stash = *{$package . '::'}{HASH};
  0            
853 0           foreach my $name (keys %$stash) {
854 0           $name = join('::', $package, $name);
855             # print "undef $name\n";
856 0           undef $$name;
857 0           undef @$name;
858 0           undef %$name;
859            
860 0           undef &$name;
861 0           undef *$name;
862             }
863 0           undef %{$package . '::'};
  0            
864             }
865            
866             sub Class::Contract::PostOBJECT::new {
867 0     0     my ($class, $posts, $original, $name) = @_;
868 0           my $objclass = ref $original;
869 0 0         carp("Warning: cannot check post-condition",
    0          
870             (@$posts==1?"":'s'),
871             " on $objclass attribute '$name'")
872             if $^W;
873 0           _free_value;
874 0           return $original;
875             }
876            
877             package Class::Contract::PostSCALAR;
878            
879             sub new {
880 0     0     my $proxy;
881 0           tie $proxy, 'Class::Contract::PostSCALAR', @_;
882 0           return \$proxy;
883             }
884            
885             sub TIESCALAR {
886 0     0     my ($class, $self, $postsubs, $original) = @_;
887 0           return bless {
888             'orig' => $original,
889             'post' => $postsubs,
890             }, $class;
891             }
892            
893 0     0     sub FETCH { return ${$_[0]->{'orig'}} }
  0            
894 0     0     sub STORE { ${$_[0]->{'orig'}} = $_[1] }
  0            
895            
896             sub DESTROY {
897 0     0     Class::Contract::generic_check('post', 'attr', @{self()}{qw(orig spec)}, @_);
  0            
898 0           Class::Contract::_free_value();
899             }
900            
901             package Class::Contract::PostARRAY;
902            
903             sub new {
904 0     0     my @proxy;
905 0           tie @proxy, 'Class::Contract::PostARRAY', @_;
906 0 0         if ($_[3]) { bless \@proxy, ref $_[2] }
  0            
907 0           return \@proxy;
908             }
909            
910             sub TIEARRAY {
911 0     0     my ($class, $self, $postsubs, $original) = @_;
912 0           return bless { 'orig' => $original,
913             'post' => $postsubs,
914             }, $class;
915             }
916            
917 0     0     sub FETCH { $_[0]->{'orig'}->[$_[1]] }
918 0     0     sub FETCHSIZE { scalar @{$_[0]->{'orig'}} }
  0            
919 0     0     sub STORE { $_[0]->{'orig'}->[$_[1]] = $_[2] }
920 0     0     sub STORESIZE { $#{$_[0]->{'orig'}} = $_[1]-1 }
  0            
921 0     0     sub EXTEND { $#{$_[0]->{'orig'}} = $_[1]-1 }
  0            
922 0     0     sub CLEAR { @{$_[0]->{'orig'}} = () }
  0            
923 0     0     sub PUSH { push @{$_[0]->{'orig'}}, @_[1..$#_] }
  0            
924 0     0     sub POP { pop @{$_[0]->{'orig'}} }
  0            
925 0     0     sub UNSHIFT { unshift @{$_[0]->{'orig'}}, @_[1..$#_] }
  0            
926 0     0     sub SHIFT { shift @{$_[0]->{'orig'}} }
  0            
927            
928             sub DESTROY {
929 0     0     Class::Contract::generic_check('post', 'attr', @{self()}{qw(orig spec)}, @_);
  0            
930 0           Class::Contract::_free_value();
931             }
932            
933            
934             package Class::Contract::PostHASH;
935            
936             sub new {
937 0     0     my %proxy;
938 0           tie %proxy, 'Class::Contract::PostHASH', @_;
939 0 0         if ($_[3]) { bless \%proxy, ref $_[2] }
  0            
940 0           return \%proxy;
941             }
942            
943             sub TIEHASH {
944 0     0     my ($class, $self, $postsubs, $original) = @_;
945 0           return bless { 'orig' => $original,
946             'post' => $postsubs,
947             }, $class;
948             }
949            
950 0     0     sub FETCH { $_[0]->{'orig'}->{$_[1]} }
951 0     0     sub STORE { $_[0]->{'orig'}->{$_[1]} = $_[2] }
952 0     0     sub EXISTS { exists $_[0]->{'orig'}->{$_[1]} }
953 0     0     sub DELETE { delete $_[0]->{'orig'}->{$_[1]} }
954 0     0     sub CLEAR { %{$_[0]->{'orig'}} = () }
  0            
955 0     0     sub FIRSTKEY { keys %{$_[0]->{'orig'}}; each %{$_[0]->{'orig'}} }
  0            
  0            
  0            
956 0     0     sub NEXTKEY { each %{$_[0]->{'orig'}} }
  0            
957            
958             sub DESTROY {
959 0     0     Class::Contract::generic_check('post', 'attr', @{self()}{qw(orig spec)}, @_);
  0            
960 0           Class::Contract::_free_value();
961             }
962             # ...NOT IN PRODUCTION
963            
964             1;
965            
966             __END__