summaryrefslogtreecommitdiff
path: root/t/op/lexsub.t
diff options
context:
space:
mode:
Diffstat (limited to 't/op/lexsub.t')
-rw-r--r--t/op/lexsub.t677
1 files changed, 677 insertions, 0 deletions
diff --git a/t/op/lexsub.t b/t/op/lexsub.t
new file mode 100644
index 0000000000..86c7e26a14
--- /dev/null
+++ b/t/op/lexsub.t
@@ -0,0 +1,677 @@
+#!perl
+
+BEGIN {
+ chdir 't';
+ @INC = '../lib';
+ require './test.pl';
+ *bar::is = *is;
+ *bar::like = *like;
+}
+no warnings 'deprecated';
+plan 128;
+
+# -------------------- Errors with feature disabled -------------------- #
+
+eval "#line 8 foo\nmy sub foo";
+is $@, qq 'Experimental "my" subs not enabled at foo line 8.\n',
+ 'my sub unexperimental error';
+eval "#line 8 foo\nCORE::state sub foo";
+is $@, qq 'Experimental "state" subs not enabled at foo line 8.\n',
+ 'state sub unexperimental error';
+eval "#line 8 foo\nour sub foo";
+is $@, qq 'Experimental "our" subs not enabled at foo line 8.\n',
+ 'our sub unexperimental error';
+
+# -------------------- our -------------------- #
+
+no warnings "experimental::lexical_subs";
+use feature 'lexical_subs';
+{
+ our sub foo { 42 }
+ is foo, 42, 'calling our sub from same package';
+ is &foo, 42, 'calling our sub from same package (amper)';
+ is do foo(), 42, 'calling our sub from same package (do)';
+ package bar;
+ sub bar::foo { 43 }
+ is foo, 42, 'calling our sub from another package';
+ is &foo, 42, 'calling our sub from another package (amper)';
+ is do foo(), 42, 'calling our sub from another package (do)';
+}
+package bar;
+is foo, 43, 'our sub falling out of scope';
+is &foo, 43, 'our sub falling out of scope (called via amper)';
+is do foo(), 43, 'our sub falling out of scope (called via amper)';
+package main;
+{
+ sub bar::a { 43 }
+ our sub a {
+ if (shift) {
+ package bar;
+ is a, 43, 'our sub invisible inside itself';
+ is &a, 43, 'our sub invisible inside itself (called via amper)';
+ is do a(), 43, 'our sub invisible inside itself (called via do)';
+ }
+ 42
+ }
+ a(1);
+ sub bar::b { 43 }
+ our sub b;
+ our sub b {
+ if (shift) {
+ package bar;
+ is b, 42, 'our sub visible inside itself after decl';
+ is &b, 42, 'our sub visible inside itself after decl (amper)';
+ is do b(), 42, 'our sub visible inside itself after decl (do)';
+ }
+ 42
+ }
+ b(1)
+}
+sub c { 42 }
+sub bar::c { 43 }
+{
+ our sub c;
+ package bar;
+ is c, 42, 'our sub foo; makes lex alias for existing sub';
+ is &c, 42, 'our sub foo; makes lex alias for existing sub (amper)';
+ is do c(), 42, 'our sub foo; makes lex alias for existing sub (do)';
+}
+{
+ our sub d;
+ sub bar::d { 'd43' }
+ package bar;
+ sub d { 'd42' }
+ is eval ::d, 'd42', 'our sub foo; applies to subsequent sub foo {}';
+}
+{
+ our sub e ($);
+ is prototype "::e", '$', 'our sub with proto';
+}
+{
+ our sub if() { 42 }
+ my $x = if if if;
+ is $x, 42, 'lexical subs (even our) override all keywords';
+ package bar;
+ my $y = if if if;
+ is $y, 42, 'our subs from other packages override all keywords';
+}
+
+# -------------------- state -------------------- #
+
+use feature 'state'; # state
+{
+ state sub foo { 44 }
+ isnt \&::foo, \&foo, 'state sub is not stored in the package';
+ is eval foo, 44, 'calling state sub from same package';
+ is eval &foo, 44, 'calling state sub from same package (amper)';
+ is eval do foo(), 44, 'calling state sub from same package (do)';
+ package bar;
+ is eval foo, 44, 'calling state sub from another package';
+ is eval &foo, 44, 'calling state sub from another package (amper)';
+ is eval do foo(), 44, 'calling state sub from another package (do)';
+}
+package bar;
+is foo, 43, 'state sub falling out of scope';
+is &foo, 43, 'state sub falling out of scope (called via amper)';
+is do foo(), 43, 'state sub falling out of scope (called via amper)';
+{
+ sub sa { 43 }
+ state sub sa {
+ if (shift) {
+ is sa, 43, 'state sub invisible inside itself';
+ is &sa, 43, 'state sub invisible inside itself (called via amper)';
+ is do sa(), 43, 'state sub invisible inside itself (called via do)';
+ }
+ 44
+ }
+ sa(1);
+ sub sb { 43 }
+ state sub sb;
+ state sub sb {
+ if (shift) {
+ # ‘state sub foo{}’ creates a new pad entry, not reusing the forward
+ # declaration. Being invisible inside itself, it sees the stub.
+ eval{sb};
+ like $@, qr/^Undefined subroutine &sb called at /,
+ 'state sub foo {} after forward declaration';
+ eval{&sb};
+ like $@, qr/^Undefined subroutine &sb called at /,
+ 'state sub foo {} after forward declaration (amper)';
+ eval{do sb()};
+ like $@, qr/^Undefined subroutine &sb called at /,
+ 'state sub foo {} after forward declaration (do)';
+ }
+ 44
+ }
+ sb(1);
+ sub sb2 { 43 }
+ state sub sb2;
+ sub sb2 {
+ if (shift) {
+ package bar;
+ is sb2, 44, 'state sub visible inside itself after decl';
+ is &sb2, 44, 'state sub visible inside itself after decl (amper)';
+ is do sb2(), 44, 'state sub visible inside itself after decl (do)';
+ }
+ 44
+ }
+ sb2(1);
+ state sub sb3;
+ {
+ state sub sb3 { # new pad entry
+ # The sub containing this comment is invisible inside itself.
+ # So this one here will assign to the outer pad entry:
+ sub sb3 { 47 }
+ }
+ }
+ is eval{sb3}, 47,
+ 'sub foo{} applying to "state sub foo;" even inside state sub foo{}';
+ # Same test again, but inside an anonymous sub
+ sub {
+ state sub sb4;
+ {
+ state sub sb4 {
+ sub sb4 { 47 }
+ }
+ }
+ is sb4, 47,
+ 'sub foo{} applying to "state sub foo;" even inside state sub foo{}';
+ }->();
+}
+sub sc { 43 }
+{
+ state sub sc;
+ eval{sc};
+ like $@, qr/^Undefined subroutine &sc called at /,
+ 'state sub foo; makes no lex alias for existing sub';
+ eval{&sc};
+ like $@, qr/^Undefined subroutine &sc called at /,
+ 'state sub foo; makes no lex alias for existing sub (amper)';
+ eval{do sc()};
+ like $@, qr/^Undefined subroutine &sc called at /,
+ 'state sub foo; makes no lex alias for existing sub (do)';
+}
+package main;
+{
+ state sub se ($);
+ is prototype eval{\&se}, '$', 'state sub with proto';
+ is prototype "se", undef, 'prototype "..." ignores state subs';
+}
+{
+ state sub if() { 44 }
+ my $x = if if if;
+ is $x, 44, 'state subs override all keywords';
+ package bar;
+ my $y = if if if;
+ is $y, 44, 'state subs from other packages override all keywords';
+}
+{
+ use warnings; no warnings "experimental::lexical_subs";
+ state $w ;
+ local $SIG{__WARN__} = sub { $w .= shift };
+ eval '#line 87 squidges
+ state sub foo;
+ state sub foo {};
+ ';
+ is $w,
+ '"state" subroutine &foo masks earlier declaration in same scope at '
+ . "squidges line 88.\n",
+ 'warning for state sub masking earlier declaration';
+}
+# Since state vars inside anonymous subs are cloned at the same time as the
+# anonymous subs containing them, the same should happen for state subs.
+sub make_closure {
+ my $x = shift;
+ sub {
+ state sub foo { $x }
+ foo
+ }
+}
+$sub1 = make_closure 48;
+$sub2 = make_closure 49;
+is &$sub1, 48, 'state sub in closure (1)';
+is &$sub2, 49, 'state sub in closure (2)';
+# But we need to test that state subs actually do persist from one invoca-
+# tion of a named sub to another (i.e., that they are not my subs).
+{
+ use warnings; no warnings "experimental::lexical_subs";
+ state $w;
+ local $SIG{__WARN__} = sub { $w .= shift };
+ eval '#line 65 teetet
+ sub foom {
+ my $x = shift;
+ state sub poom { $x }
+ eval{\&poom}
+ }
+ ';
+ is $w, "Variable \"\$x\" will not stay shared at teetet line 67.\n",
+ 'state subs get "Variable will not stay shared" messages';
+ my $poom = foom(27);
+ my $poom2 = foom(678);
+ is eval{$poom->()}, eval {$poom2->()},
+ 'state subs close over the first outer my var, like pkg subs';
+ my $x = 43;
+ for $x (765) {
+ state sub etetetet { $x }
+ is eval{etetetet}, 43, 'state sub ignores for() localisation';
+ }
+}
+# And we also need to test that multiple state subs can close over each
+# other’s entries in the parent subs pad, and that cv_clone is not con-
+# fused by that.
+sub make_anon_with_state_sub{
+ sub {
+ state sub s1;
+ state sub s2 { \&s1 }
+ sub s1 { \&s2 }
+ if (@_) { return \&s1 }
+ is s1,\&s2, 'state sub in anon closure closing over sibling state sub';
+ is s2,\&s1, 'state sub in anon closure closing over sibling state sub';
+ }
+}
+{
+ my $s = make_anon_with_state_sub;
+ &$s;
+
+ # And make sure the state subs were actually cloned.
+ isnt make_anon_with_state_sub->(0), &$s(0),
+ 'state subs in anon subs are cloned';
+ is &$s(0), &$s(0), 'but only when the anon sub is cloned';
+}
+{
+ state sub BEGIN { exit };
+ pass 'state subs are never special blocks';
+ state sub END { shift }
+ is eval{END('jkqeudth')}, jkqeudth,
+ 'state sub END {shift} implies @_, not @ARGV';
+}
+{
+ state sub redef {}
+ use warnings; no warnings "experimental::lexical_subs";
+ state $w;
+ local $SIG{__WARN__} = sub { $w .= shift };
+ eval "#line 56 pygpyf\nsub redef {}";
+ is $w, "Subroutine redef redefined at pygpyf line 56.\n",
+ "sub redefinition warnings from state subs";
+}
+{
+ state sub p (\@) {
+ is ref $_[0], 'ARRAY', 'state sub with proto';
+ }
+ p(my @a);
+}
+{
+ state sub x;
+ eval 'sub x {3}';
+ is x, 3, 'state sub defined inside eval';
+
+ sub r {
+ state sub foo { 3 };
+ if (@_) { # outer call
+ r();
+ is foo(), 42,
+ 'state sub run-time redefinition applies to all recursion levels';
+ }
+ else { # inner call
+ eval 'sub foo { 42 }';
+ }
+ }
+ r(1);
+}
+
+# -------------------- my -------------------- #
+
+{
+ my sub foo { 44 }
+ isnt \&::foo, \&foo, 'my sub is not stored in the package';
+ is foo, 44, 'calling my sub from same package';
+ is &foo, 44, 'calling my sub from same package (amper)';
+ is do foo(), 44, 'calling my sub from same package (do)';
+ package bar;
+ is foo, 44, 'calling my sub from another package';
+ is &foo, 44, 'calling my sub from another package (amper)';
+ is do foo(), 44, 'calling my sub from another package (do)';
+}
+package bar;
+is foo, 43, 'my sub falling out of scope';
+is &foo, 43, 'my sub falling out of scope (called via amper)';
+is do foo(), 43, 'my sub falling out of scope (called via amper)';
+{
+ sub ma { 43 }
+ my sub ma {
+ if (shift) {
+ is ma, 43, 'my sub invisible inside itself';
+ is &ma, 43, 'my sub invisible inside itself (called via amper)';
+ is do ma(), 43, 'my sub invisible inside itself (called via do)';
+ }
+ 44
+ }
+ ma(1);
+ sub mb { 43 }
+ my sub mb;
+ my sub mb {
+ if (shift) {
+ # ‘my sub foo{}’ creates a new pad entry, not reusing the forward
+ # declaration. Being invisible inside itself, it sees the stub.
+ eval{mb};
+ like $@, qr/^Undefined subroutine &mb called at /,
+ 'my sub foo {} after forward declaration';
+ eval{&mb};
+ like $@, qr/^Undefined subroutine &mb called at /,
+ 'my sub foo {} after forward declaration (amper)';
+ eval{do mb()};
+ like $@, qr/^Undefined subroutine &mb called at /,
+ 'my sub foo {} after forward declaration (do)';
+ }
+ 44
+ }
+ mb(1);
+ sub mb2 { 43 }
+ my sub sb2;
+ sub mb2 {
+ if (shift) {
+ package bar;
+ is mb2, 44, 'my sub visible inside itself after decl';
+ is &mb2, 44, 'my sub visible inside itself after decl (amper)';
+ is do mb2(), 44, 'my sub visible inside itself after decl (do)';
+ }
+ 44
+ }
+ mb2(1);
+ my sub mb3;
+ {
+ my sub mb3 { # new pad entry
+ # The sub containing this comment is invisible inside itself.
+ # So this one here will assign to the outer pad entry:
+ sub mb3 { 47 }
+ }
+ }
+ is eval{mb3}, 47,
+ 'sub foo{} applying to "my sub foo;" even inside my sub foo{}';
+ # Same test again, but inside an anonymous sub
+ sub {
+ my sub mb4;
+ {
+ my sub mb4 {
+ sub mb4 { 47 }
+ }
+ }
+ is mb4, 47,
+ 'sub foo{} applying to "my sub foo;" even inside my sub foo{}';
+ }->();
+}
+sub mc { 43 }
+{
+ my sub mc;
+ eval{mc};
+ like $@, qr/^Undefined subroutine &mc called at /,
+ 'my sub foo; makes no lex alias for existing sub';
+ eval{&mc};
+ like $@, qr/^Undefined subroutine &mc called at /,
+ 'my sub foo; makes no lex alias for existing sub (amper)';
+ eval{do mc()};
+ like $@, qr/^Undefined subroutine &mc called at /,
+ 'my sub foo; makes no lex alias for existing sub (do)';
+}
+package main;
+{
+ my sub me ($);
+ is prototype eval{\&me}, '$', 'my sub with proto';
+ is prototype "me", undef, 'prototype "..." ignores my subs';
+}
+{
+ my sub if() { 44 }
+ my $x = if if if;
+ is $x, 44, 'my subs override all keywords';
+ package bar;
+ my $y = if if if;
+ is $y, 44, 'my subs from other packages override all keywords';
+}
+{
+ use warnings; no warnings "experimental::lexical_subs";
+ my $w ;
+ local $SIG{__WARN__} = sub { $w .= shift };
+ eval '#line 87 squidges
+ my sub foo;
+ my sub foo {};
+ ';
+ is $w,
+ '"my" subroutine &foo masks earlier declaration in same scope at '
+ . "squidges line 88.\n",
+ 'warning for my sub masking earlier declaration';
+}
+# Test that my subs are cloned inside anonymous subs.
+sub mmake_closure {
+ my $x = shift;
+ sub {
+ my sub foo { $x }
+ foo
+ }
+}
+$sub1 = mmake_closure 48;
+$sub2 = mmake_closure 49;
+is &$sub1, 48, 'my sub in closure (1)';
+is &$sub2, 49, 'my sub in closure (2)';
+# Test that they are cloned in named subs.
+{
+ use warnings; no warnings "experimental::lexical_subs";
+ my $w;
+ local $SIG{__WARN__} = sub { $w .= shift };
+ eval '#line 65 teetet
+ sub mfoom {
+ my $x = shift;
+ my sub poom { $x }
+ \&poom
+ }
+ ';
+ is $w, undef, 'my subs get no "Variable will not stay shared" messages';
+ my $poom = mfoom(27);
+ my $poom2 = mfoom(678);
+ is $poom->(), 27, 'my subs closing over outer my var (1)';
+ is $poom2->(), 678, 'my subs closing over outer my var (2)';
+ my $x = 43;
+ my sub aoeu;
+ for $x (765) {
+ my sub etetetet { $x }
+ sub aoeu { $x }
+ is etetetet, 765, 'my sub respects for() localisation';
+ is aoeu, 43, 'unless it is declared outside the for loop';
+ }
+}
+# And we also need to test that multiple my subs can close over each
+# other’s entries in the parent subs pad, and that cv_clone is not con-
+# fused by that.
+sub make_anon_with_my_sub{
+ sub {
+ my sub s1;
+ my sub s2 { \&s1 }
+ sub s1 { \&s2 }
+ if (@_) { return eval { \&s1 } }
+ is eval{s1},eval{\&s2}, 'my sub in anon closure closing over sibling my sub';
+ is eval{s2},eval{\&s1}, 'my sub in anon closure closing over sibling my sub';
+ }
+}
+
+# Test my subs inside predeclared my subs
+{
+ my sub s2;
+ sub s2 {
+ my $x = 3;
+ my sub s3 { eval '$x' }
+ s3;
+ }
+ is s2, 3, 'my sub inside predeclared my sub';
+}
+
+{
+ my $s = make_anon_with_my_sub;
+ &$s;
+
+ # And make sure the my subs were actually cloned.
+ isnt make_anon_with_my_sub->(0), &$s(0),
+ 'my subs in anon subs are cloned';
+ isnt &$s(0), &$s(0), 'at each invocation of the enclosing sub';
+}
+{
+ my sub BEGIN { exit };
+ pass 'my subs are never special blocks';
+ my sub END { shift }
+ is END('jkqeudth'), jkqeudth,
+ 'my sub END {shift} implies @_, not @ARGV';
+}
+{
+ my sub redef {}
+ use warnings; no warnings "experimental::lexical_subs";
+ my $w;
+ local $SIG{__WARN__} = sub { $w .= shift };
+ eval "#line 56 pygpyf\nsub redef {}";
+ is $w, "Subroutine redef redefined at pygpyf line 56.\n",
+ "sub redefinition warnings from my subs";
+
+ undef $w;
+ sub {
+ my sub x {};
+ sub { eval "#line 87 khaki\n\\&x" }
+ }->()();
+ is $w, "Subroutine \"&x\" is not available at khaki line 87.\n",
+ "unavailability warning during compilation of eval in closure";
+
+ undef $w;
+ no warnings 'void';
+ eval <<'->()();';
+#line 87 khaki
+ sub {
+ my sub x{}
+ sub not_lexical8 {
+ \&x
+ }
+ }
+->()();
+ is $w, "Subroutine \"&x\" is not available at khaki line 90.\n",
+ "unavailability warning during compilation of named sub in anon";
+
+ undef $w;
+ sub not_lexical9 {
+ my sub x {};
+ format =
+@
+&x
+.
+ }
+ eval { write };
+ my($f,$l) = (__FILE__,__LINE__ - 1);
+ is $w, "Subroutine \"&x\" is not available at $f line $l.\n",
+ 'unavailability warning during cloning';
+ $l -= 3;
+ is $@, "Undefined subroutine &x called at $f line $l.\n",
+ 'Vivified sub is correctly named';
+}
+sub not_lexical10 {
+ my sub foo;
+ foo();
+ sub not_lexical11 {
+ my sub bar {
+ my $x = 'khaki car keys for the khaki car';
+ not_lexical10();
+ sub foo {
+ is $x, 'khaki car keys for the khaki car',
+ 'mysubs in inner clonables use the running clone of their CvOUTSIDE'
+ }
+ }
+ bar()
+ }
+}
+not_lexical11();
+{
+ my sub p (\@) {
+ is ref $_[0], 'ARRAY', 'my sub with proto';
+ }
+ p(my @a);
+}
+{
+ my sub x;
+ my $count;
+ sub x { x() if $count++ < 10 }
+ x();
+ is $count, 11, 'my recursive subs';
+}
+{
+ my sub x;
+ eval 'sub x {3}';
+ is x, 3, 'my sub defined inside eval';
+}
+
+{
+ state $w;
+ local $SIG{__WARN__} = sub { $w .= shift };
+ eval q{ my sub george () { 2 } };
+ is $w, undef, 'no double free from constant my subs';
+}
+
+# -------------------- Interactions (and misc tests) -------------------- #
+
+is sub {
+ my sub s1;
+ my sub s2 { 3 };
+ sub s1 { state sub foo { \&s2 } foo }
+ s1
+ }->()(), 3, 'state sub inside my sub closing over my sub uncle';
+
+{
+ my sub s2 { 3 };
+ sub not_lexical { state sub foo { \&s2 } foo }
+ is not_lexical->(), 3, 'state subs that reference my sub from outside';
+}
+
+# Test my subs inside predeclared package subs
+# This test also checks that CvOUTSIDE pointers are not mangled when the
+# inner sub’s CvOUTSIDE points to another sub.
+sub not_lexical2;
+sub not_lexical2 {
+ my $x = 23;
+ my sub bar;
+ sub not_lexical3 {
+ not_lexical2();
+ sub bar { $x }
+ };
+ bar
+}
+is not_lexical3, 23, 'my subs inside predeclared package subs';
+
+# Test my subs inside predeclared package sub, where the lexical sub is
+# declared outside the package sub.
+# This checks that CvOUTSIDE pointers are fixed up even when the sub is
+# not declared inside the sub that its CvOUTSIDE points to.
+sub not_lexical5 {
+ my sub foo;
+ sub not_lexical4;
+ sub not_lexical4 {
+ my $x = 234;
+ not_lexical5();
+ sub foo { $x }
+ }
+ foo
+}
+is not_lexical4, 234,
+ 'my sub defined in predeclared pkg sub but declared outside';
+
+undef *not_lexical6;
+{
+ my sub foo;
+ sub not_lexical6 { sub foo { } }
+ pass 'no crash when cloning a mysub declared inside an undef pack sub';
+}
+
+undef &not_lexical7;
+eval 'sub not_lexical7 { my @x }';
+{
+ my sub foo;
+ foo();
+ sub not_lexical7 {
+ state $x;
+ sub foo {
+ is ref \$x, 'SCALAR',
+ "redeffing a mysub's outside does not make it use the wrong pad"
+ }
+ }
+}