Changing the test count is a good idea.
[p5sagit/p5-mst-13.2.git] / lib / fields.pm
CommitLineData
458fb581 1package fields;
2
024bc14b 3=head1 NAME
4
5fields - compile-time class fields
6
7=head1 SYNOPSIS
8
9 {
10 package Foo;
11 use fields qw(foo bar _Foo_private);
12 sub new {
13 my Foo $self = shift;
14 unless (ref $self) {
15 $self = fields::new($self);
16 $self->{_Foo_private} = "this is Foo's secret";
17 }
18 $self->{foo} = 10;
19 $self->{bar} = 20;
20 return $self;
21 }
22 }
23
24 my $var = Foo->new;
25 $var->{foo} = 42;
26
27 # this will generate an error
28 $var->{zap} = 42;
29
30 # subclassing
31 {
32 package Bar;
33 use base 'Foo';
34 use fields qw(baz _Bar_private); # not shared with Foo
35 sub new {
36 my $class = shift;
37 my $self = fields::new($class);
38 $self->SUPER::new(); # init base fields
39 $self->{baz} = 10; # init own fields
40 $self->{_Bar_private} = "this is Bar's secret";
41 return $self;
42 }
43 }
44
45=head1 DESCRIPTION
46
47The C<fields> pragma enables compile-time verified class fields.
48
49NOTE: The current implementation keeps the declared fields in the %FIELDS
50hash of the calling package, but this may change in future versions.
51Do B<not> update the %FIELDS hash directly, because it must be created
52at compile-time for it to be fully useful, as is done by this pragma.
53
024bc14b 54The related C<base> pragma will combine fields from base classes and any
55fields declared using the C<fields> pragma. This enables field
56inheritance to work properly.
57
58Field names that start with an underscore character are made private to
59the class and are not visible to subclasses. Inherited fields can be
60overridden but will generate a warning if used together with the C<-w>
61switch.
62
024bc14b 63The following functions are supported:
64
65=over 8
66
67=item new
68
67edfcd9 69fields::new() creates and blesses a restricted-hash comprised of the
70fields declared using the C<fields> pragma into the specified class.
024bc14b 71This makes it possible to write a constructor like this:
72
73 package Critter::Sounds;
74 use fields qw(cat dog bird);
75
76 sub new {
77 my $self = shift;
78 $self = fields::new($self) unless ref $self;
79 $self->{cat} = 'meow'; # scalar element
80 @$self{'dog','bird'} = ('bark','tweet'); # slice
81 return $self;
82 }
83
84=item phash
85
67edfcd9 86Pseudo-hashes have been removed from Perl as of 5.10. Consider using
87restricted hashes instead. Using fields::phash() will cause an error.
024bc14b 88
67edfcd9 89=back
024bc14b 90
67edfcd9 91=head1 SEE ALSO
024bc14b 92
67edfcd9 93L<base>,
024bc14b 94
67edfcd9 95=cut
024bc14b 96
67edfcd9 97use 5.006_001;
98use strict;
99no strict 'refs';
100use warnings::register;
101our(%attr, $VERSION);
024bc14b 102
67edfcd9 103$VERSION = "1.04";
024bc14b 104
67edfcd9 105use Hash::Util qw(lock_keys);
024bc14b 106
67edfcd9 107# some constants
108sub _PUBLIC () { 1 }
109sub _PRIVATE () { 2 }
024bc14b 110
67edfcd9 111# The %attr hash holds the attributes of the currently assigned fields
112# per class. The hash is indexed by class names and the hash value is
113# an array reference. The first element in the array is the lowest field
114# number not belonging to a base class. The remaining elements' indices
115# are the field numbers. The values are integer bit masks, or undef
116# in the case of base class private fields (which occupy a slot but are
117# otherwise irrelevant to the class).
024bc14b 118
67edfcd9 119sub import {
120 my $class = shift;
121 return unless @_;
122 my $package = caller(0);
123 # avoid possible typo warnings
124 %{"$package\::FIELDS"} = () unless %{"$package\::FIELDS"};
125 my $fields = \%{"$package\::FIELDS"};
126 my $fattr = ($attr{$package} ||= [1]);
127 my $next = @$fattr;
024bc14b 128
67edfcd9 129 if ($next > $fattr->[0]
130 and ($fields->{$_[0]} || 0) >= $fattr->[0])
131 {
132 # There are already fields not belonging to base classes.
133 # Looks like a possible module reload...
134 $next = $fattr->[0];
135 }
136 foreach my $f (@_) {
137 my $fno = $fields->{$f};
024bc14b 138
67edfcd9 139 # Allow the module to be reloaded so long as field positions
140 # have not changed.
141 if ($fno and $fno != $next) {
142 require Carp;
143 if ($fno < $fattr->[0]) {
144 warnings::warnif("Hides field '$f' in base class") ;
145 } else {
146 Carp::croak("Field name '$f' already in use");
147 }
148 }
149 $fields->{$f} = $next;
150 $fattr->[$next] = ($f =~ /^_/) ? _PRIVATE : _PUBLIC;
151 $next += 1;
152 }
153 if (@$fattr > $next) {
154 # Well, we gave them the benefit of the doubt by guessing the
155 # module was reloaded, but they appear to be declaring fields
156 # in more than one place. We can't be sure (without some extra
157 # bookkeeping) that the rest of the fields will be declared or
158 # have the same positions, so punt.
159 require Carp;
160 Carp::croak ("Reloaded module must declare all fields at once");
161 }
162}
163
164sub inherit { # called by base.pm when $base_fields is nonempty
165 my($derived, $base) = @_;
166 my $base_attr = $attr{$base};
167 my $derived_attr = $attr{$derived} ||= [];
168 # avoid possible typo warnings
169 %{"$base\::FIELDS"} = () unless %{"$base\::FIELDS"};
170 %{"$derived\::FIELDS"} = () unless %{"$derived\::FIELDS"};
171 my $base_fields = \%{"$base\::FIELDS"};
172 my $derived_fields = \%{"$derived\::FIELDS"};
173
174 $derived_attr->[0] = $base_attr ? scalar(@$base_attr) : 1;
175 while (my($k,$v) = each %$base_fields) {
176 my($fno);
177 if ($fno = $derived_fields->{$k} and $fno != $v) {
178 require Carp;
179 Carp::croak ("Inherited %FIELDS can't override existing %FIELDS");
180 }
181 if ($base_attr->[$v] & _PRIVATE) {
182 $derived_attr->[$v] = undef;
183 } else {
184 $derived_attr->[$v] = $base_attr->[$v];
185 $derived_fields->{$k} = $v;
186 }
187 }
188}
189
190sub _dump # sometimes useful for debugging
191{
192 for my $pkg (sort keys %attr) {
193 print "\n$pkg";
194 if (@{"$pkg\::ISA"}) {
195 print " (", join(", ", @{"$pkg\::ISA"}), ")";
196 }
197 print "\n";
198 my $fields = \%{"$pkg\::FIELDS"};
199 for my $f (sort {$fields->{$a} <=> $fields->{$b}} keys %$fields) {
200 my $no = $fields->{$f};
201 print " $no: $f";
202 my $fattr = $attr{$pkg}[$no];
203 if (defined $fattr) {
204 my @a;
205 push(@a, "public") if $fattr & _PUBLIC;
206 push(@a, "private") if $fattr & _PRIVATE;
207 push(@a, "inherited") if $no < $attr{$pkg}[0];
208 print "\t(", join(", ", @a), ")";
209 }
210 print "\n";
211 }
212 }
213}
214
215sub new {
216 my $class = shift;
217 $class = ref $class if ref $class;
218 my $self = bless {}, $class;
219 lock_keys(%$self, keys %{$class.'::FIELDS'});
220 return $self;
221}
222
223sub phash {
224 die "Pseudo-hashes have been removed from Perl";
225}
226
2271;