Commit | Line | Data |
---|---|---|
0b2df18f FC |
1 | #!perl |
2 | ||
3 | # Test the various op trees that turn sub () { ... } into a constant, and | |
4 | # some variants that don’t. | |
5 | ||
6 | BEGIN { | |
7 | chdir 't'; | |
8 | require './test.pl'; | |
624c42e2 | 9 | set_up_inc('../lib'); |
0b2df18f | 10 | } |
6dfba0aa | 11 | plan 168; |
0b2df18f | 12 | |
bbd7756c FC |
13 | # @tests is an array of hash refs, each of which can have various keys: |
14 | # | |
15 | # nickname - name of the sub to use in test names | |
16 | # generator - a sub returning a code ref to test | |
a41f70ad | 17 | # finally - sub to run after the tests |
bbd7756c FC |
18 | # |
19 | # Each of the following gives expected test results. If the key is | |
20 | # omitted, the test is skipped: | |
21 | # | |
22 | # retval - the returned code ref’s return value | |
23 | # same_retval - whether the same scalar is returned each time | |
24 | # inlinable - whether the sub is inlinable | |
25 | # deprecated - whether the sub returning a code ref will emit a depreca- | |
26 | # tion warning when called | |
27 | # method - whether the sub has the :method attribute | |
0b2df18f | 28 | |
bbd7756c FC |
29 | # [perl #63540] Don’t treat sub { if(){.....}; "constant" } as a constant |
30 | sub blonk { ++$blonk_was_called } | |
31 | push @tests, { | |
32 | nickname => 'sub with null+kids (if-block), then constant', | |
33 | generator => sub { | |
34 | # This used to turn into a constant with the value of $x | |
35 | my $x = 7; | |
36 | sub() { if($x){ () = "tralala"; blonk() }; 0 } | |
37 | }, | |
38 | retval => 0, | |
39 | same_retval => 0, | |
40 | inlinable => 0, | |
41 | deprecated => 0, | |
42 | method => 0, | |
43 | finally => sub { ok($blonk_was_called, 'RT #63540'); }, | |
44 | }; | |
0b2df18f FC |
45 | |
46 | # [perl #79908] | |
bbd7756c FC |
47 | push @tests, { |
48 | nickname => 'sub with simple lexical modified elsewhere', | |
49 | generator => sub { my $x = 5; my $ret = sub(){$x}; $x = 7; $ret }, | |
50 | retval => 5, # change to 7 when the deprecation cycle is over | |
51 | same_retval => 0, | |
52 | inlinable => 1, | |
53 | deprecated => 1, | |
54 | method => 0, | |
55 | }; | |
56 | ||
57 | push @tests, { | |
cb12c7ef FC |
58 | nickname => 'sub with simple lexical unmodified elsewhere', |
59 | generator => sub { my $x = 5; sub(){$x} }, | |
60 | retval => 5, | |
61 | same_retval => 0, | |
62 | inlinable => 1, | |
63 | deprecated => 0, | |
64 | method => 0, | |
65 | }; | |
66 | ||
67 | push @tests, { | |
68 | nickname => 'return $variable modified elsewhere', | |
69 | generator => sub { my $x=5; my $ret = sub(){return $x}; $x = 7; $ret }, | |
70 | retval => 7, | |
71 | same_retval => 0, | |
72 | inlinable => 0, | |
73 | deprecated => 0, | |
74 | method => 0, | |
75 | }; | |
76 | ||
77 | push @tests, { | |
78 | nickname => 'return $variable unmodified elsewhere', | |
79 | generator => sub { my $x = 5; sub(){return $x} }, | |
80 | retval => 5, | |
81 | same_retval => 0, | |
82 | inlinable => 0, | |
83 | deprecated => 0, | |
84 | method => 0, | |
85 | }; | |
86 | ||
87 | push @tests, { | |
7f08c641 FC |
88 | nickname => 'sub () { 0; $x } with $x modified elsewhere', |
89 | generator => sub { my $x = 5; my $ret = sub(){0;$x}; $x = 8; $ret }, | |
90 | retval => 8, | |
91 | same_retval => 0, | |
92 | inlinable => 0, | |
93 | deprecated => 0, | |
94 | method => 0, | |
95 | }; | |
96 | ||
97 | push @tests, { | |
98 | nickname => 'sub () { 0; $x } with $x unmodified elsewhere', | |
99 | generator => sub { my $x = 5; my $y = $x; sub(){0;$x} }, | |
100 | retval => 5, | |
101 | same_retval => 0, | |
102 | inlinable => 1, | |
103 | deprecated => 0, | |
104 | method => 0, | |
105 | }; | |
106 | ||
3c37c25a FC |
107 | # Explicit return after optimised statement, not at end of sub |
108 | push @tests, { | |
109 | nickname => 'sub () { 0; return $x; ... }', | |
110 | generator => sub { my $x = 5; sub () { 0; return $x; ... } }, | |
111 | retval => 5, | |
112 | same_retval => 0, | |
113 | inlinable => 0, | |
114 | deprecated => 0, | |
115 | method => 0, | |
116 | }; | |
117 | ||
9d391755 FC |
118 | # Explicit return after optimised statement, at end of sub [perl #123092] |
119 | push @tests, { | |
120 | nickname => 'sub () { 0; return $x }', | |
121 | generator => sub { my $x = 5; sub () { 0; return $x } }, | |
122 | retval => 5, | |
123 | same_retval => 0, | |
124 | inlinable => 0, | |
125 | deprecated => 0, | |
126 | method => 0, | |
127 | }; | |
128 | ||
f1603422 FC |
129 | # Multiple closure tests |
130 | push @tests, { | |
131 | nickname => 'simple lexical after another closure and no lvalue', | |
132 | generator => sub { | |
133 | my $x = 5; | |
134 | # This closure prevents inlining, though theoretically it shouldn’t | |
135 | # have to. If you change the behaviour, just change the test. This | |
136 | # fails the refcount check in op.c:op_const_sv, which is necessary for | |
137 | # the sake of \(my $x = 1) (tested below). | |
138 | my $sub1 = sub () { () = $x }; | |
139 | sub () { $x }; | |
140 | }, | |
141 | retval => 5, | |
142 | same_retval => 0, | |
143 | inlinable => 0, | |
144 | deprecated => 0, | |
145 | method => 0, | |
146 | }; | |
147 | push @tests, { | |
148 | nickname => 'simple lexical before another closure and no lvalue', | |
149 | generator => sub { | |
150 | my $x = 5; | |
151 | my $ret = sub () { $x }; | |
152 | # This does not prevent inlining and never has. | |
153 | my $sub1 = sub () { () = $x }; | |
154 | $ret; | |
155 | }, | |
156 | retval => 5, | |
157 | same_retval => 0, | |
158 | inlinable => 1, | |
159 | deprecated => 0, | |
160 | method => 0, | |
161 | }; | |
162 | push @tests, { | |
163 | nickname => 'simple lexical after an lvalue closure', | |
164 | generator => sub { | |
165 | my $x = 5; | |
166 | # This has always prevented inlining | |
167 | my $sub1 = sub () { $x++ }; | |
168 | sub () { $x }; | |
169 | }, | |
170 | retval => 5, | |
171 | same_retval => 0, | |
172 | inlinable => 0, | |
173 | deprecated => 0, | |
174 | method => 0, | |
175 | }; | |
176 | push @tests, { | |
177 | nickname => 'simple lexical before an lvalue closure', | |
178 | generator => sub { | |
179 | my $x = 5; | |
180 | my $ret = sub () { $x }; # <-- simple lexical op tree | |
181 | # Traditionally this has not prevented inlining, though it should. But | |
182 | # since $ret has a simple lexical op tree, we preserve backward-compat- | |
183 | # ibility, but deprecate it. | |
184 | my $sub1 = sub () { $x++ }; | |
185 | $ret; | |
186 | }, | |
187 | retval => 5, | |
188 | same_retval => 0, | |
189 | inlinable => 1, | |
190 | deprecated => 1, | |
191 | method => 0, | |
192 | }; | |
193 | push @tests, { | |
194 | nickname => 'complex lexical op tree before an lvalue closure', | |
195 | generator => sub { | |
196 | my $x = 5; | |
197 | my $ret = sub () { 0; $x }; # <-- more than just a lexical | |
198 | # This used not to prevent inlining, though it should, and now does. | |
199 | my $sub1 = sub () { $x++ }; | |
200 | $ret; | |
201 | }, | |
202 | retval => 5, | |
203 | same_retval => 0, | |
204 | inlinable => 0, | |
205 | deprecated => 0, | |
206 | method => 0, | |
207 | }; | |
208 | push @tests, { | |
209 | nickname => 'complex lexical op tree before a nested lvalue closure', | |
210 | generator => sub { | |
211 | my $x = 5; | |
212 | my $ret = sub () { 0; $x }; # <-- more than just a lexical | |
213 | # This used not to prevent inlining, though it should, and now does. | |
214 | my $sub1 = sub () { sub () { $x++ } }; # nested | |
215 | $ret; | |
216 | }, | |
217 | retval => 5, | |
218 | same_retval => 0, | |
219 | inlinable => 0, | |
220 | deprecated => 0, | |
221 | method => 0, | |
222 | }; | |
223 | ||
4a991780 FC |
224 | use feature 'state', 'lexical_subs'; |
225 | no warnings 'experimental::lexical_subs'; | |
226 | ||
73c13e16 FC |
227 | # Constant constants |
228 | push @tests, { | |
229 | nickname => 'sub with constant', | |
230 | generator => sub { sub () { 8 } }, | |
231 | retval => 8, | |
232 | same_retval => 0, | |
233 | inlinable => 1, | |
234 | deprecated => 0, | |
235 | method => 0, | |
236 | }; | |
237 | push @tests, { | |
238 | nickname => 'sub with constant and return', | |
239 | generator => sub { sub () { return 8 } }, | |
240 | retval => 8, | |
241 | same_retval => 0, | |
242 | inlinable => 0, | |
243 | deprecated => 0, | |
244 | method => 0, | |
245 | }; | |
246 | push @tests, { | |
247 | nickname => 'sub with optimised statement and constant', | |
248 | generator => sub { sub () { 0; 8 } }, | |
249 | retval => 8, | |
250 | same_retval => 0, | |
251 | inlinable => 1, | |
252 | deprecated => 0, | |
253 | method => 0, | |
254 | }; | |
255 | push @tests, { | |
256 | nickname => 'sub with optimised statement, constant and return', | |
257 | generator => sub { sub () { 0; return 8 } }, | |
258 | retval => 8, | |
259 | same_retval => 0, | |
260 | inlinable => 0, | |
261 | deprecated => 0, | |
262 | method => 0, | |
263 | }; | |
264 | push @tests, { | |
265 | nickname => 'my sub with constant', | |
266 | generator => sub { my sub x () { 8 } \&x }, | |
267 | retval => 8, | |
268 | same_retval => 0, | |
269 | inlinable => 1, | |
270 | deprecated => 0, | |
271 | method => 0, | |
272 | }; | |
273 | push @tests, { | |
274 | nickname => 'my sub with constant and return', | |
275 | generator => sub { my sub x () { return 8 } \&x }, | |
276 | retval => 8, | |
277 | same_retval => 0, | |
278 | inlinable => 0, | |
279 | deprecated => 0, | |
280 | method => 0, | |
281 | }; | |
282 | push @tests, { | |
283 | nickname => 'my sub with optimised statement and constant', | |
284 | generator => sub { my sub x () { 0; 8 } \&x }, | |
285 | retval => 8, | |
286 | same_retval => 0, | |
287 | inlinable => 1, | |
288 | deprecated => 0, | |
289 | method => 0, | |
290 | }; | |
291 | push @tests, { | |
292 | nickname => 'my sub with optimised statement, constant and return', | |
293 | generator => sub { my sub x () { 0; return 8 } \&x }, | |
294 | retval => 8, | |
295 | same_retval => 0, | |
296 | inlinable => 0, | |
297 | deprecated => 0, | |
298 | method => 0, | |
299 | }; | |
300 | ||
03414f05 FC |
301 | # String eval |
302 | push @tests, { | |
303 | nickname => 'sub () { $x } with eval in scope', | |
304 | generator => sub { | |
305 | my $outer = 43; | |
306 | my $ret = sub () { $outer }; | |
307 | eval '$outer++'; | |
308 | $ret; | |
309 | }, | |
310 | retval => 43, | |
311 | same_retval => 0, | |
312 | inlinable => 1, | |
313 | deprecated => 1, | |
314 | method => 0, | |
315 | }; | |
316 | push @tests, { | |
317 | nickname => 'sub () { $x } with s///ee in scope', | |
318 | generator => sub { | |
319 | my $outer = 43; | |
320 | my $dummy = '$outer++'; | |
321 | my $ret = sub () { $outer }; | |
322 | $dummy =~ s//$dummy/ee; | |
323 | $ret; | |
324 | }, | |
325 | retval => 43, | |
326 | same_retval => 0, | |
327 | inlinable => 1, | |
328 | deprecated => 1, | |
329 | method => 0, | |
330 | }; | |
331 | push @tests, { | |
332 | nickname => 'sub () { $x } with eval not in scope', | |
333 | generator => sub { | |
334 | my $ret; | |
335 | { | |
336 | my $outer = 43; | |
337 | $ret = sub () { $outer }; | |
338 | } | |
339 | eval ''; | |
340 | $ret; | |
341 | }, | |
342 | retval => 43, | |
343 | same_retval => 0, | |
344 | inlinable => 1, | |
345 | deprecated => 0, | |
346 | method => 0, | |
347 | }; | |
348 | ||
4a991780 FC |
349 | push @tests, { |
350 | nickname => 'sub () { my $x; state sub z { $x } $outer }', | |
351 | generator => sub { | |
352 | my $outer = 43; | |
353 | sub () { my $x; state sub z { $x } $outer } | |
354 | }, | |
355 | retval => 43, | |
356 | same_retval => 0, | |
f1603422 FC |
357 | inlinable => 0, |
358 | deprecated => 0, | |
359 | method => 0, | |
360 | }; | |
361 | ||
362 | push @tests, { | |
363 | nickname => 'closure after \(my $x=1)', | |
364 | generator => sub { | |
365 | $y = \(my $x = 1); | |
366 | my $ret = sub () { $x }; | |
367 | $$y += 7; | |
368 | $ret; | |
369 | }, | |
370 | retval => 8, | |
371 | same_retval => 0, | |
4a991780 FC |
372 | inlinable => 0, |
373 | deprecated => 0, | |
374 | method => 0, | |
375 | }; | |
376 | ||
7f08c641 | 377 | push @tests, { |
bbd7756c FC |
378 | nickname => 'sub:method with simple lexical', |
379 | generator => sub { my $y; sub():method{$y} }, | |
380 | retval => undef, | |
381 | same_retval => 0, | |
382 | inlinable => 1, | |
383 | deprecated => 0, | |
384 | method => 1, | |
385 | }; | |
7a3e5b7e FC |
386 | push @tests, { |
387 | nickname => 'sub:method with constant', | |
388 | generator => sub { sub():method{3} }, | |
389 | retval => 3, | |
390 | same_retval => 0, | |
391 | inlinable => 1, | |
392 | deprecated => 0, | |
393 | method => 1, | |
394 | }; | |
395 | push @tests, { | |
396 | nickname => 'my sub:method with constant', | |
397 | generator => sub { my sub x ():method{3} \&x }, | |
398 | retval => 3, | |
399 | same_retval => 0, | |
400 | inlinable => 1, | |
401 | deprecated => 0, | |
402 | method => 1, | |
403 | }; | |
bbd7756c | 404 | |
e4211fee FC |
405 | push @tests, { |
406 | nickname => 'sub closing over state var', | |
407 | generator => sub { state $x = 3; sub () {$x} }, | |
408 | retval => 3, | |
409 | same_retval => 0, | |
410 | inlinable => 1, | |
411 | deprecated => 0, | |
412 | method => 0, | |
413 | }; | |
414 | push @tests, { | |
415 | nickname => 'sub closing over state var++', | |
416 | generator => sub { state $x++; sub () { $x } }, | |
417 | retval => 1, | |
418 | same_retval => 0, | |
419 | inlinable => 1, | |
420 | deprecated => 1, | |
421 | method => 0, | |
422 | }; | |
423 | ||
bbd7756c FC |
424 | |
425 | use feature 'refaliasing'; | |
426 | no warnings 'experimental::refaliasing'; | |
427 | for \%_ (@tests) { | |
428 | my $nickname = $_{nickname}; | |
0ac016fc | 429 | my $w; |
bbd7756c FC |
430 | local $SIG{__WARN__} = sub { $w = shift }; |
431 | my $sub = &{$_{generator}}; | |
432 | if (exists $_{deprecated}) { | |
433 | if ($_{deprecated}) { | |
434 | like $w, qr/^Constants from lexical variables potentially (?x: | |
9840d1d6 A |
435 | )modified elsewhere are deprecated\. This will (?x: |
436 | )not be allowed in Perl 5\.32 at /, | |
bbd7756c FC |
437 | "$nickname is deprecated"; |
438 | } | |
439 | else { | |
440 | is $w, undef, "$nickname is not deprecated"; | |
441 | } | |
442 | } | |
443 | if (exists $_{retval}) { | |
444 | is &$sub, $_{retval}, "retval of $nickname"; | |
445 | } | |
446 | if (exists $_{same_retval}) { | |
447 | my $same = $_{same_retval} ? "same" : "different"; | |
448 | &{$_{same_retval} ? \&is : \&isnt}( | |
449 | \scalar &$sub(), \scalar &$sub(), | |
450 | "$nickname gives $same retval each call" | |
451 | ); | |
452 | } | |
453 | if (exists $_{inlinable}) { | |
454 | local *temp_inlinability_test = $sub; | |
455 | $w = undef; | |
456 | use warnings 'redefine'; | |
457 | *temp_inlinability_test = sub (){}; | |
458 | my $S = $_{inlinable} ? "Constant s" : "S"; | |
459 | my $not = " not" x! $_{inlinable}; | |
460 | like $w, qr/^${S}ubroutine .* redefined at /, | |
461 | "$nickname is$not inlinable"; | |
462 | } | |
463 | if (exists $_{method}) { | |
464 | local *time = $sub; | |
465 | $w = undef; | |
466 | use warnings 'ambiguous'; | |
467 | eval "()=time"; | |
468 | if ($_{method}) { | |
469 | is $w, undef, "$nickname has :method attribute"; | |
470 | } | |
471 | else { | |
472 | like $w, qr/^Ambiguous call resolved as CORE::time\(\), (?x: | |
473 | )qualify as such or use & at /, | |
474 | "$nickname has no :method attribute"; | |
475 | } | |
0b2df18f | 476 | } |
0b2df18f | 477 | |
bbd7756c FC |
478 | &{$_{finally} or next} |
479 | } | |
82e85a9c FC |
480 | |
481 | # This used to fail an assertion in leave_scope. For some reason, it did | |
482 | # not fail within the framework above. | |
483 | sub { my $x = "x"; my $sub = sub () { $x }; undef $sub; } ->(); | |
484 | pass("No assertion failure when turning on PADSTALE on lexical shared by" | |
485 | ." erstwhile constant"); | |
486 | ||
6dfba0aa FC |
487 | { |
488 | my $sub = sub { | |
489 | my $x = "x"x2000; sub () {$x}; | |
490 | }->(); | |
491 | $y = &$sub; | |
492 | $z = &$sub; | |
493 | is $z, $y, 'inlinable sub ret vals are not swipable'; | |
494 | } |