xref: /openbsd-src/gnu/usr.bin/perl/t/class/field.t (revision 5486feefcc8cb79b19e014ab332cc5dfd05b3b33)
1#!./perl
2
3BEGIN {
4    chdir 't' if -d 't';
5    require './test.pl';
6    set_up_inc('../lib');
7    require Config;
8}
9
10use v5.36;
11use feature 'class';
12no warnings 'experimental::class';
13
14# We can't test fields in isolation without having at least one method to
15# use them from. We'll try to keep most of the heavy testing of method
16# abilities to t/class/method.t
17
18# field in method
19{
20    class Testcase1 {
21        field $f;
22        method incr { return ++$f; }
23    }
24
25    my $obj = Testcase1->new;
26    $obj->incr;
27    is($obj->incr, 2, 'Field $f incremented twice');
28
29    my $obj2 = Testcase1->new;
30    is($obj2->incr, 1, 'Fields are distinct between instances');
31}
32
33# fields are distinct
34{
35    class Testcase2 {
36        field $x;
37        field $y;
38
39        method setpos { $x = $_[0]; $y = $_[1] }
40        method x      { return $x; }
41        method y      { return $y; }
42    }
43
44    my $obj = Testcase2->new;
45    $obj->setpos(10, 20);
46    is($obj->x, 10, '$pos->x');
47    is($obj->y, 20, '$pos->y');
48}
49
50# fields of all variable types
51{
52    class Testcase3 {
53        field $s;
54        field @a;
55        field %h;
56
57        method setup {
58            $s = "scalar";
59            @a = ( "array" );
60            %h = ( key => "hash" );
61            return $self; # test chaining
62        }
63        method test {
64            ::is($s,      "scalar", 'scalar storage');
65            ::is($a[0],   "array",  'array storage');
66            ::is($h{key}, "hash",   'hash storage');
67        }
68    }
69
70    Testcase3->new->setup->test;
71}
72
73# fields can be captured by anon subs
74{
75    class Testcase4 {
76        field $count;
77
78        method make_incrsub {
79            return sub { $count++ };
80        }
81
82        method count { return $count }
83    }
84
85    my $obj = Testcase4->new;
86    my $incr = $obj->make_incrsub;
87
88    $incr->();
89    $incr->();
90    $incr->();
91
92    is($obj->count, 3, '$obj->count after invoking closure x 3');
93}
94
95# fields can be captured by anon methods
96{
97    class Testcase5 {
98        field $count;
99
100        method make_incrmeth {
101            return method { $count++ };
102        }
103
104        method count { return $count }
105    }
106
107    my $obj = Testcase5->new;
108    my $incr = $obj->make_incrmeth;
109
110    $obj->$incr;
111    $obj->$incr;
112    $obj->$incr;
113
114    is($obj->count, 3, '$obj->count after invoking method-closure x 3');
115}
116
117# fields of multiple unit classes are distinct
118{
119    class Testcase6::A;
120    field $x = "A";
121    method m { return "unit-$x" }
122
123    class Testcase6::B;
124    field $x = "B";
125    method m { return "unit-$x" }
126
127    package main;
128    ok(eq_array([Testcase6::A->new->m, Testcase6::B->new->m], ["unit-A", "unit-B"]),
129        'Fields of multiple unit classes remain distinct');
130}
131
132# fields can be initialised with constant expressions
133{
134    class Testcase7 {
135        field $scalar = 123;
136        method scalar { return $scalar; }
137
138        field @array = (4, 5, 6);
139        method array { return @array; }
140
141        field %hash  = (7 => 89);
142        method hash { return %hash; }
143    }
144
145    my $obj = Testcase7->new;
146
147    is($obj->scalar, 123, 'Scalar field can be constant initialised');
148
149    ok(eq_array([$obj->array], [4, 5, 6]), 'Array field can be constant initialised');
150
151    ok(eq_hash({$obj->hash}, {7 => 89}), 'Hash field can be constant initialised');
152}
153
154# field initialiser expressions are evaluated within the constructor of each
155# instance
156{
157    my $next_x = 1;
158    my @items;
159    my %mappings;
160
161    class Testcase8 {
162        field $x = $next_x++;
163        method x { return $x; }
164
165        field @y = ("more", @items);
166        method y { return @y; }
167
168        field %z = (first => "value", %mappings);
169        method z { return %z; }
170    }
171
172    is($next_x, 1, '$next_x before any objects');
173
174    @items = ("values");
175    $mappings{second} = "here";
176
177    my $obj1 = Testcase8->new;
178    my $obj2 = Testcase8->new;
179
180    is($obj1->x, 1, 'Object 1 has x == 1');
181    is($obj2->x, 2, 'Object 2 has x == 2');
182
183    is($next_x, 3, '$next_x after constructing two');
184
185    ok(eq_array([$obj1->y], ["more", "values"]),
186        'Object 1 has correct array field');
187    ok(eq_hash({$obj1->z}, {first => "value", second => "here"}),
188        'Object 1 has correct hash field');
189}
190
191# fields are visible during initialiser expressions of later fields
192{
193    class Testcase9 {
194        field $one   = 1;
195        field $two   = $one + 1;
196        field $three = $two + 1;
197
198        field @four = $one;
199        field @five = (@four, $two, $three);
200        field @six  = grep { $_ > 1 } @five;
201
202        method three { return $three; }
203
204        method six { return @six; }
205    }
206
207    my $obj = Testcase9->new;
208    is($obj->three, 3, 'Scalar fields initialised from earlier fields');
209    ok(eq_array([$obj->six], [2, 3]), 'Array fields initialised from earlier fields');
210}
211
212# fields can take :param attributes to consume constructor parameters
213{
214    my $next_gamma = 4;
215
216    class Testcase10 {
217        field $alpha :param        = undef;
218        field $beta  :param        = 123;
219        field $gamma :param(delta) = $next_gamma++;
220
221        method values { return ($alpha, $beta, $gamma); }
222    }
223
224    my $obj = Testcase10->new(
225        alpha => "A",
226        beta  => "B",
227        delta => "G",
228    );
229    ok(eq_array([$obj->values], [qw(A B G)]),
230        'Field initialised by :params');
231    is($next_gamma, 4, 'Defaulting expression not evaluated for passed value');
232
233    $obj = Testcase10->new();
234    ok(eq_array([$obj->values], [undef, 123, 4]),
235        'Field initialised by defaulting expressions');
236    is($next_gamma, 5, 'Defaulting expression evaluated for missing value');
237}
238
239# fields can be made non-optional
240{
241    class Testcase11 {
242        field $x :param;
243        field $y :param;
244    }
245
246    Testcase11->new(x => 1, y => 1);
247
248    ok(!eval { Testcase11->new(x => 2) },
249        'Constructor fails without y');
250    like($@, qr/^Required parameter 'y' is missing for "Testcase11" constructor at /,
251        'Failure from missing y argument');
252}
253
254# field assignment expressions on :param can use //= and ||=
255{
256    class Testcase12 {
257        field $if_exists  :param(e)   = "DEF";
258        field $if_defined :param(d) //= "DEF";
259        field $if_true    :param(t) ||= "DEF";
260
261        method values { return ($if_exists, $if_defined, $if_true); }
262    }
263
264    ok(eq_array(
265        [Testcase12->new(e => "yes", d => "yes", t => "yes")->values],
266        ["yes", "yes", "yes"]),
267        'Values for "yes"');
268
269    ok(eq_array(
270        [Testcase12->new(e => 0, d => 0, t => 0)->values],
271        [0, 0, "DEF"]),
272        'Values for 0');
273
274    ok(eq_array(
275        [Testcase12->new(e => undef, d => undef, t => undef)->values],
276        [undef, "DEF", "DEF"]),
277        'Values for undef');
278
279    ok(eq_array(
280        [Testcase12->new()->values],
281        ["DEF", "DEF", "DEF"]),
282        'Values for missing');
283}
284
285# field initialiser expressions permit `goto` in do {} blocks
286{
287    class Testcase13 {
288        field $forwards = do { goto HERE; HERE: 1 };
289        field $backwards = do { my $x; HERE: ; goto HERE if !$x++; 2 };
290
291        method values { return ($forwards, $backwards) }
292    }
293
294    ok(eq_array(
295        [Testcase13->new->values],
296        [1, 2],
297        'Values for goto inside do {} blocks in field initialisers'));
298}
299
300# field initialiser expressions permit a __CLASS__
301{
302    class Testcase14 {
303        field $classname = __CLASS__;
304
305        method classname { return $classname }
306    }
307
308    is(Testcase14->new->classname, "Testcase14", '__CLASS__ in field initialisers');
309}
310
311done_testing;
312