xref: /openbsd-src/gnu/usr.bin/perl/t/op/state.t (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1#!./perl -w
2# tests state variables
3
4BEGIN {
5    chdir 't' if -d 't';
6    @INC = '../lib';
7    require './test.pl';
8}
9
10use strict;
11
12plan tests => 137;
13
14# Before loading feature.pm, test it with CORE::
15ok eval 'CORE::state $x = 1;', 'CORE::state outside of feature.pm scope';
16
17
18use feature ":5.10";
19
20
21ok( ! defined state $uninit, q(state vars are undef by default) );
22
23# basic functionality
24
25sub stateful {
26    state $x;
27    state $y = 1;
28    my $z = 2;
29    state ($t) //= 3;
30    return ($x++, $y++, $z++, $t++);
31}
32
33my ($x, $y, $z, $t) = stateful();
34is( $x, 0, 'uninitialized state var' );
35is( $y, 1, 'initialized state var' );
36is( $z, 2, 'lexical' );
37is( $t, 3, 'initialized state var, list syntax' );
38
39($x, $y, $z, $t) = stateful();
40is( $x, 1, 'incremented state var' );
41is( $y, 2, 'incremented state var' );
42is( $z, 2, 'reinitialized lexical' );
43is( $t, 4, 'incremented state var, list syntax' );
44
45($x, $y, $z, $t) = stateful();
46is( $x, 2, 'incremented state var' );
47is( $y, 3, 'incremented state var' );
48is( $z, 2, 'reinitialized lexical' );
49is( $t, 5, 'incremented state var, list syntax' );
50
51# in a nested block
52
53sub nesting {
54    state $foo = 10;
55    my $t;
56    { state $bar = 12; $t = ++$bar }
57    ++$foo;
58    return ($foo, $t);
59}
60
61($x, $y) = nesting();
62is( $x, 11, 'outer state var' );
63is( $y, 13, 'inner state var' );
64
65($x, $y) = nesting();
66is( $x, 12, 'outer state var' );
67is( $y, 14, 'inner state var' );
68
69# in a closure
70
71sub generator {
72    my $outer;
73    # we use $outer to generate a closure
74    sub { ++$outer; ++state $x }
75}
76
77my $f1 = generator();
78is( $f1->(), 1, 'generator 1' );
79is( $f1->(), 2, 'generator 1' );
80my $f2 = generator();
81is( $f2->(), 1, 'generator 2' );
82is( $f1->(), 3, 'generator 1 again' );
83is( $f2->(), 2, 'generator 2 once more' );
84
85# with ties
86{
87    package countfetches;
88    our $fetchcount = 0;
89    sub TIESCALAR {bless {}};
90    sub FETCH { ++$fetchcount; 18 };
91    tie my $y, "countfetches";
92    sub foo { state $x = $y; $x++ }
93    ::is( foo(), 18, "initialisation with tied variable" );
94    ::is( foo(), 19, "increments correctly" );
95    ::is( foo(), 20, "increments correctly, twice" );
96    ::is( $fetchcount, 1, "fetch only called once" );
97}
98
99# state variables are shared among closures
100
101sub gen_cashier {
102    my $amount = shift;
103    state $cash_in_store = 0;
104    return {
105	add => sub { $cash_in_store += $amount },
106	del => sub { $cash_in_store -= $amount },
107	bal => sub { $cash_in_store },
108    };
109}
110
111gen_cashier(59)->{add}->();
112gen_cashier(17)->{del}->();
113is( gen_cashier()->{bal}->(), 42, '$42 in my drawer' );
114
115# stateless assignment to a state variable
116
117sub stateless {
118    state $reinitme = 42;
119    ++$reinitme;
120}
121is( stateless(), 43, 'stateless function, first time' );
122is( stateless(), 44, 'stateless function, second time' );
123
124# array state vars
125
126sub stateful_array {
127    state @x;
128    push @x, 'x';
129    return $#x;
130}
131
132my $xsize = stateful_array();
133is( $xsize, 0, 'uninitialized state array' );
134
135$xsize = stateful_array();
136is( $xsize, 1, 'uninitialized state array after one iteration' );
137
138# hash state vars
139
140sub stateful_hash {
141    state %hx;
142    return $hx{foo}++;
143}
144
145my $xhval = stateful_hash();
146is( $xhval, 0, 'uninitialized state hash' );
147
148$xhval = stateful_hash();
149is( $xhval, 1, 'uninitialized state hash after one iteration' );
150
151# Recursion
152
153sub noseworth {
154    my $level = shift;
155    state $recursed_state = 123;
156    is($recursed_state, 123, "state kept through recursion ($level)");
157    noseworth($level - 1) if $level;
158}
159noseworth(2);
160
161# Assignment return value
162
163sub pugnax { my $x = state $y = 42; $y++; $x; }
164
165is( pugnax(), 42, 'scalar state assignment return value' );
166is( pugnax(), 43, 'scalar state assignment return value' );
167
168
169#
170# Test various blocks.
171#
172foreach my $x (1 .. 3) {
173    state $y = $x;
174    is ($y, 1, "foreach $x");
175}
176
177for (my $x = 1; $x < 4; $x ++) {
178    state $y = $x;
179    is ($y, 1, "for $x");
180}
181
182while ($x < 4) {
183    state $y = $x;
184    is ($y, 1, "while $x");
185    $x ++;
186}
187
188$x = 1;
189until ($x >= 4) {
190    state $y = $x;
191    is ($y, 1, "until $x");
192    $x ++;
193}
194
195$x = 0;
196$y = 0;
197{
198    state $z = $x;
199    $z ++;
200    $y ++;
201    is ($z, $y, "bare block $y");
202    redo if $y < 3
203}
204
205
206#
207# Check state $_
208#
209my @stones = qw [fred wilma barny betty];
210my $first  = $stones [0];
211my $First  = ucfirst $first;
212$_ = "bambam";
213foreach my $flint (@stones) {
214    no warnings 'experimental::lexical_topic';
215    state $_ = $flint;
216    is $_, $first, 'state $_';
217    ok /$first/, '/.../ binds to $_';
218    is ucfirst, $First, '$_ default argument';
219}
220is $_, "bambam", '$_ is still there';
221
222#
223# Goto.
224#
225my @simpsons = qw [Homer Marge Bart Lisa Maggie];
226again:
227    my $next = shift @simpsons;
228    state $simpson = $next;
229    is $simpson, 'Homer', 'goto 1';
230    goto again if @simpsons;
231
232my $vi;
233{
234    goto Elvis unless $vi;
235           state $calvin = ++ $vi;
236    Elvis: state $vile   = ++ $vi;
237    redo unless defined $calvin;
238    is $calvin, 2, "goto 2";
239    is $vile,   1, "goto 3";
240    is $vi,     2, "goto 4";
241}
242my @presidents = qw [Taylor Garfield Ford Arthur Monroe];
243sub president {
244    my $next = shift @presidents;
245    state $president = $next;
246    goto  &president if @presidents;
247    $president;
248}
249my $president_answer = $presidents [0];
250is president, $president_answer, '&goto';
251
252my @flowers = qw [Bluebonnet Goldenrod Hawthorn Peony];
253foreach my $f (@flowers) {
254    goto state $flower = $f;
255    ok 0, 'computed goto 0'; next;
256    Bluebonnet: ok 1, 'computed goto 1'; next;
257    Goldenrod:  ok 0, 'computed goto 2'; next;
258    Hawthorn:   ok 0, 'computed goto 3'; next;
259    Peony:      ok 0, 'computed goto 4'; next;
260    ok 0, 'computed goto 5'; next;
261}
262
263#
264# map/grep
265#
266my @apollo  = qw [Eagle Antares Odyssey Aquarius];
267my @result1 = map  {state $x = $_;}     @apollo;
268my @result2 = grep {state $x = /Eagle/} @apollo;
269{
270    local $" = "";
271    is "@result1", $apollo [0] x @apollo, "map";
272    is "@result2", "@apollo", "grep";
273}
274
275#
276# Reference to state variable.
277#
278sub reference {\state $x}
279my $ref1 = reference;
280my $ref2 = reference;
281is $ref1, $ref2, "Reference to state variable";
282
283#
284# Pre/post increment.
285#
286foreach my $x (1 .. 3) {
287    ++ state $y;
288    state $z ++;
289    is $y, $x, "state pre increment";
290    is $z, $x, "state post increment";
291}
292
293
294#
295# Substr
296#
297my $tintin = "Tin-Tin";
298my @thunderbirds  = qw [Scott Virgel Alan Gordon John];
299my @thunderbirds2 = qw [xcott xxott xxxtt xxxxt xxxxx];
300foreach my $x (0 .. 4) {
301    state $c = \substr $tintin, $x, 1;
302    my $d = \substr ((state $tb = $thunderbirds [$x]), $x, 1);
303    $$c = "x";
304    $$d = "x";
305    is $tintin, "xin-Tin", "substr";
306    is $tb, $thunderbirds2 [$x], "substr";
307}
308
309
310#
311# Use with given.
312#
313my @spam = qw [spam ham bacon beans];
314foreach my $spam (@spam) {
315    no warnings 'experimental::smartmatch';
316    given (state $spam = $spam) {
317        when ($spam [0]) {ok 1, "given"}
318        default          {ok 0, "given"}
319    }
320}
321
322#
323# Redefine.
324#
325{
326    state $x = "one";
327    no warnings;
328    state $x = "two";
329    is $x, "two", "masked"
330}
331
332# normally closureless anon subs share a CV and pad. If the anon sub has a
333# state var, this would mean that it is shared. Check that this doesn't
334# happen
335
336{
337    my @f;
338    push @f, sub { state $x; ++$x } for 1..2;
339    $f[0]->() for 1..10;
340    is $f[0]->(), 11;
341    is $f[1]->(), 1;
342}
343
344# each copy of an anon sub should get its own 'once block'
345
346{
347    my $x; # used to force a closure
348    my @f;
349    push @f, sub { $x=0; state $s = $_[0]; $s } for 1..2;
350    is $f[0]->(1), 1;
351    is $f[0]->(2), 1;
352    is $f[1]->(3), 3;
353    is $f[1]->(4), 3;
354}
355
356
357
358
359foreach my $forbidden (<DATA>) {
360    chomp $forbidden;
361    no strict 'vars';
362    eval $forbidden;
363    like $@, qr/Initialization of state variables in list context currently forbidden/, "Currently forbidden: $forbidden";
364}
365
366# [perl #49522] state variable not available
367
368{
369    my @warnings;
370    local $SIG{__WARN__} = sub { push @warnings, $_[0] };
371
372    eval q{
373	use warnings;
374
375	sub f_49522 {
376	    state $s = 88;
377	    sub g_49522 { $s }
378	    sub { $s };
379	}
380
381	sub h_49522 {
382	    state $t = 99;
383	    sub i_49522 {
384		sub { $t };
385	    }
386	}
387    };
388    is $@, '', "eval f_49522";
389    # shouldn't be any 'not available' or 'not stay shared' warnings
390    ok !@warnings, "suppress warnings part 1 [@warnings]";
391
392    @warnings = ();
393    my $f = f_49522();
394    is $f->(), 88, "state var closure 1";
395    is g_49522(), 88, "state var closure 2";
396    ok !@warnings, "suppress warnings part 2 [@warnings]";
397
398
399    @warnings = ();
400    $f = i_49522();
401    h_49522(); # initialise $t
402    is $f->(), 99, "state var closure 3";
403    ok !@warnings, "suppress warnings part 3 [@warnings]";
404
405
406}
407
408
409# [perl #117095] state var initialisation getting skipped
410# the 'if 0' code below causes a call to op_free at compile-time,
411# which used to inadvertently mark the state var as initialised.
412
413{
414    state $f = 1;
415    foo($f) if 0; # this calls op_free on padmy($f)
416    ok(defined $f, 'state init not skipped');
417}
418
419# [perl #121134] Make sure padrange doesn't mess with these
420{
421    sub thing {
422	my $expect = shift;
423        my ($x, $y);
424        state $z;
425
426        is($z, $expect, "State variable is correct");
427
428        $z = 5;
429    }
430
431    thing(undef);
432    thing(5);
433
434    sub thing2 {
435        my $expect = shift;
436        my $x;
437        my $y;
438        state $z;
439
440        is($z, $expect, "State variable is correct");
441
442        $z = 6;
443    }
444
445    thing2(undef);
446    thing2(6);
447}
448
449# [perl #123029] regression in "state" under PERL_NO_COW
450sub rt_123029 {
451    state $s;
452    $s = 'foo'x500;
453    my $c = $s;
454    return defined $s;
455}
456ok(rt_123029(), "state variables don't surprisingly disappear when accessed");
457
458__DATA__
459state ($a) = 1;
460(state $a) = 1;
461state @a = 1;
462state (@a) = 1;
463(state @a) = 1;
464state %a = ();
465state (%a) = ();
466(state %a) = ();
467state ($a, $b) = ();
468state ($a, @b) = ();
469(state $a, state $b) = ();
470(state $a, $b) = ();
471(state $a, my $b) = ();
472(state $a, state @b) = ();
473(state $a, local @b) = ();
474(state $a, undef, state $b) = ();
475state ($a, undef, $b) = ();
476