summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorDavid Mitchell <davem@iabyn.com>2017-02-24 14:32:28 +0000
committerDavid Mitchell <davem@iabyn.com>2017-06-05 12:52:18 +0100
commit82ab48fa8c6c51b80c9b69419850061640110339 (patch)
tree6261756cecf64bec07b4c28e12e6df4258ad754a /lib
parent0a30b526db0a91f5cad699185aecc65e6eef7965 (diff)
downloadperl-82ab48fa8c6c51b80c9b69419850061640110339.tar.gz
Deparse my var attributes correctly
Formerly this: my $x :foo; deparsed as 'attributes'->import('main', \$x, 'foo'), my $x; it now deparses as: my $x :foo; It handles all the common forms, such as my Foo::Bar ($s, @a, %h) :foo(foo1) bar(bar1); but doesn't yet handle an attribute declaration that's not a statement, e.g. f(1, $x :foo); Under TEST -deparse, this fixes the following unexpectedly failing scripts: ../dist/IO/t/io_file_export.t ../dist/IO/t/io_multihomed.t ../dist/IO/t/io_udp.t ../dist/Thread-Queue/t/02_refs.t ../dist/Thread-Semaphore/t/01_basic.t ../dist/Thread-Semaphore/t/04_nonblocking.t ../dist/Thread-Semaphore/t/05_force.t ../dist/Thread-Semaphore/t/06_timed.t ../dist/threads-shared/t/av_refs.t ../dist/threads-shared/t/blessed.t ../dist/threads-shared/t/clone.t ../dist/threads-shared/t/cond.t ../dist/threads-shared/t/dualvar.t ../dist/threads-shared/t/hv_refs.t ../dist/threads-shared/t/object.t ../dist/threads-shared/t/object2.t ../dist/threads-shared/t/shared_attr.t ../dist/threads-shared/t/sv_refs.t ../dist/threads-shared/t/utf8.t ../dist/threads-shared/t/wait.t ../dist/threads-shared/t/waithires.t ../dist/threads/t/err.t ../dist/threads/t/free.t ../dist/threads/t/join.t ../dist/threads/t/kill.t ../dist/threads/t/kill2.t ../dist/threads/t/libc.t ../dist/threads/t/problems.t ../dist/threads/t/state.t op/threads-dirh.t and fixes the following expected-to-fail scripts: ../dist/Thread-Queue/t/08_nothreads.t ../dist/threads/t/exit.t ../dist/threads/t/thread.t op/attrs.t op/getpid.t
Diffstat (limited to 'lib')
-rw-r--r--lib/B/Deparse.pm165
-rw-r--r--lib/B/Deparse.t22
2 files changed, 187 insertions, 0 deletions
diff --git a/lib/B/Deparse.pm b/lib/B/Deparse.pm
index dd61739260..bf45482635 100644
--- a/lib/B/Deparse.pm
+++ b/lib/B/Deparse.pm
@@ -3535,9 +3535,167 @@ BEGIN {
delete @uses_intro{qw( lvref lvrefslice lvavref entersub )};
}
+
+# Look for a my attribute declaration in a list or ex-list. Returns undef
+# if not found, 'my($x, @a) :Foo(bar)' etc otherwise.
+#
+# There are three basic tree structs that are expected:
+#
+# my $x :foo;
+# <1> ex-list vK/LVINTRO ->c
+# <0> ex-pushmark v ->3
+# <1> entersub[t2] vKRS*/TARG ->b
+# ....
+# <0> padsv[$x:64,65] vM/LVINTRO ->c
+#
+# my @a :foo;
+# my %h :foo;
+#
+# <1> ex-list vK ->c
+# <0> ex-pushmark v ->3
+# <0> padav[@a:64,65] vM/LVINTRO ->4
+# <1> entersub[t2] vKRS*/TARG ->c
+# ....
+#
+# my ($x,@a,%h) :foo;
+#
+# <;> nextstate(main 64 -e:1) v:{ ->3
+# <@> list vKP ->w
+# <0> pushmark vM/LVINTRO ->4
+# <0> padsv[$x:64,65] vM/LVINTRO ->5
+# <0> padav[@a:64,65] vM/LVINTRO ->6
+# <0> padhv[%h:64,65] vM/LVINTRO ->7
+# <1> entersub[t4] vKRS*/TARG ->f
+# ....
+# <1> entersub[t5] vKRS*/TARG ->n
+# ....
+# <1> entersub[t6] vKRS*/TARG ->v
+# ....
+# where the entersub in all cases looks like
+# <1> entersub[t2] vKRS*/TARG ->c
+# <0> pushmark s ->5
+# <$> const[PV "attributes"] sM ->6
+# <$> const[PV "main"] sM ->7
+# <1> srefgen sKM/1 ->9
+# <1> ex-list lKRM ->8
+# <0> padsv[@a:64,65] sRM ->8
+# <$> const[PV "foo"] sM ->a
+# <.> method_named[PV "import"] ->b
+
+sub maybe_my_attr {
+ my ($self, $op, $cx) = @_;
+
+ my $kid = $op->first->sibling; # skip pushmark
+ return if class($kid) eq 'NULL';
+
+ my $lop;
+ my $type;
+
+ # Extract out all the pad ops and entersub ops into
+ # @padops and @entersubops. Return if anything else seen.
+ # Also determine what class (if any) all the pad vars belong to
+ my $class;
+ my (@padops, @entersubops);
+ for ($lop = $kid; !null($lop); $lop = $lop->sibling) {
+ my $lopname = $lop->name;
+ my $loppriv = $lop->private;
+ if ($lopname =~ /^pad[sah]v$/) {
+ return unless $loppriv & OPpLVAL_INTRO;
+ return if $loppriv & OPpPAD_STATE;
+
+ my $padname = $self->padname_sv($lop->targ);
+ my $thisclass = ($padname->FLAGS & SVpad_TYPED)
+ ? $padname->SvSTASH->NAME : 'main';
+
+ # all pad vars must be in the same class
+ $class //= $thisclass;
+ return unless $thisclass eq $class;
+
+ push @padops, $lop;
+ }
+ elsif ($lopname eq 'entersub') {
+ push @entersubops, $lop;
+ }
+ else {
+ return;
+ }
+ }
+
+ return unless @padops && @padops == @entersubops;
+
+ # there should be a balance: each padop has a corresponding
+ # 'attributes'->import() method call, in the same order.
+
+ my @varnames;
+ my $attr_text;
+
+ for my $i (0..$#padops) {
+ my $padop = $padops[$i];
+ my $esop = $entersubops[$i];
+
+ push @varnames, $self->padname($padop->targ);
+
+ return unless ($esop->flags & OPf_KIDS);
+
+ my $kid = $esop->first;
+ return unless $kid->type == OP_PUSHMARK;
+
+ $kid = $kid->sibling;
+ return unless $$kid && $kid->type == OP_CONST;
+ return unless $self->const_sv($kid)->PV eq 'attributes';
+
+ $kid = $kid->sibling;
+ return unless $$kid && $kid->type == OP_CONST; # __PACKAGE__
+
+ $kid = $kid->sibling;
+ return unless $$kid
+ && $kid->name eq "srefgen"
+ && ($kid->flags & OPf_KIDS)
+ && ($kid->first->flags & OPf_KIDS)
+ && $kid->first->first->name =~ /^pad[sah]v$/
+ && $kid->first->first->targ == $padop->targ;
+
+ $kid = $kid->sibling;
+ my @attr;
+ while ($$kid) {
+ last if ($kid->type != OP_CONST);
+ push @attr, $self->const_sv($kid)->PV;
+ $kid = $kid->sibling;
+ }
+ return unless @attr;
+ my $thisattr = ":" . join(' ', @attr);
+ $attr_text //= $thisattr;
+ # all import calls must have the same list of attributes
+ return unless $attr_text eq $thisattr;
+
+ return unless $kid->name eq 'method_named';
+ return unless $self->meth_sv($kid)->PV eq 'import';
+
+ $kid = $kid->sibling;
+ return if $$kid;
+ }
+
+ my $res = 'my';
+ $res .= " $class " if $class ne 'main';
+ $res .=
+ (@varnames > 1)
+ ? "(" . join(', ', @varnames) . ')'
+ : " $varnames[0]";
+
+ return "$res $attr_text";
+}
+
+
sub pp_list {
my $self = shift;
my($op, $cx) = @_;
+
+ {
+ # might be my ($s,@a,%h) :Foo(bar);
+ my $my_attr = maybe_my_attr($self, $op, $cx);
+ return $my_attr if defined $my_attr;
+ }
+
my($expr, @exprs);
my $kid = $op->first->sibling; # skip pushmark
return '' if class($kid) eq 'NULL';
@@ -3831,6 +3989,13 @@ sub _op_is_or_was {
sub pp_null {
my($self, $op, $cx) = @_;
+
+ # might be 'my $s :Foo(bar);'
+ if ($op->targ == OP_LIST) {
+ my $my_attr = maybe_my_attr($self, $op, $cx);
+ return $my_attr if defined $my_attr;
+ }
+
if (class($op) eq "OP") {
# old value is lost
return $self->{'ex_const'} if $op->targ == OP_CONST;
diff --git a/lib/B/Deparse.t b/lib/B/Deparse.t
index 4361967e6e..ab03ed7235 100644
--- a/lib/B/Deparse.t
+++ b/lib/B/Deparse.t
@@ -2627,3 +2627,25 @@ my $r2 = qr/$a(?{ my($x, $y) = (); })/;
/a\ b/x;
/a\ b/;
/a\ b/x;
+####
+# my attributes
+my $s1 :foo(f1, f2) bar(b1, b2);
+my @a1 :foo(f1, f2) bar(b1, b2);
+my %h1 :foo(f1, f2) bar(b1, b2);
+my($s2, @a2, %h2) :foo(f1, f2) bar(b1, b2);
+####
+# my class attributes
+package Foo::Bar;
+my Foo::Bar $s1 :foo(f1, f2) bar(b1, b2);
+my Foo::Bar @a1 :foo(f1, f2) bar(b1, b2);
+my Foo::Bar %h1 :foo(f1, f2) bar(b1, b2);
+my Foo::Bar ($s2, @a2, %h2) :foo(f1, f2) bar(b1, b2);
+package main;
+my Foo::Bar $s3 :foo(f1, f2) bar(b1, b2);
+my Foo::Bar @a3 :foo(f1, f2) bar(b1, b2);
+my Foo::Bar %h3 :foo(f1, f2) bar(b1, b2);
+my Foo::Bar ($s4, @a4, %h4) :foo(f1, f2) bar(b1, b2);
+####
+# avoid false positives in my $x :attribute
+'attributes'->import('main', \my $x1, 'foo(bar)'), my $y1;
+'attributes'->import('Fooo', \my $x2, 'foo(bar)'), my $y2;