develooper Front page | perl.perl5.porters | Postings from October 2016

Re: (\@a) = \($x,$y) in non-void context

Thread Previous | Thread Next
From:
Dave Mitchell
Date:
October 21, 2016 15:09
Subject:
Re: (\@a) = \($x,$y) in non-void context
Message ID:
20161021150835.GD3128@iabyn.com
On Wed, Oct 19, 2016 at 04:22:51AM +0200, Aristotle Pagaltzis wrote:
> There appears to be only one single mention of what list assignment in
> list context does across the entire documentation, in perlop.pod:
> 
>     Similarly, a list assignment in list context produces the list of
>     lvalues assigned to, and a list assignment in scalar context returns
>     the number of elements produced by the expression on the right hand
>     side of the assignment.
> 
> This reads to me like
> 
>     ( $foo, $bar, @baz ) = 1..5
> 
> is supposed to return the same lvalues as this expression:
> 
>     ( $foo, $bar, $baz[0], $baz[1], $baz[2] )
> 
> Question is, what ought this return?
> 
>     ( @foo, $bar, @baz ) = 1..3
> 
> $foo will be undef after this assignment, so clearly it is “assigned to”
> per the documentation text? And @baz will be cleared as well, however no
> scalar inside it will be assigned to as such. Thus, the list of lvalues
> returned must be this:
> 
>     ( $foo[0], $foo[1], $foo[2], $bar )
> 
> Right? But that seems odd to me too. It is consistent, but is it really
> the intent?

Well, perl already intentionally includes unassigned-to scalars in the
non-slurpy case:

    @a =     (($a,$b,$c,$d) = 1..2);  # @a contains 4 elements, and ..
    $_++ for (($a,$b,$c,$d) = 1..2);  # ... a,b,c,d now (2,3,1,1)

and I think it's a bug that 'empty' scalars before a slurpy aren't passed
through:

    @a = (($a,$b,$c,@d) = 1..2);  # @a contains 2 elements: $c not passed

and though rarer, I think for consistency, scalars following a slurpy
should be visible too:

    @a = (($a,$b,@c,$d) = 1..2);  # @a contains 2 elements: $d not passed

> I think we need to nail down what the behaviour is supposed to be before
> we decide what exactly is a bug or not… and we also ought to have tests
> for this that cover all the cases.

I've just pushed a branch for smoking, smoke-me/davem/aassign
that has two main commits: the first optimises the hell out of aggregate
assignment (which I've been working on for the last 3 weeks and why I
keep spotting these edge cases), and the second fixes the above according
to my interpretation of what's right.

That second commit (f74b019aa3e535907e5a088862089ed18b189dcb) contains a
lot of new tests, many of which would fail before the fix. It it also
deliberately breaks some existing tests.

What follows is (1) the commit message, (2) the new tests (which all pass in
that branch) and (3) the test failures from those tests under the old
regime.

commit f74b019aa3e535907e5a088862089ed18b189dcb
Author:     David Mitchell <davem@iabyn.com>
AuthorDate: Wed Oct 19 09:41:53 2016 +0100
Commit:     David Mitchell <davem@iabyn.com>
CommitDate: Fri Oct 21 13:05:42 2016 +0100

    Handle list assignment in list context better
    
    In something like
    
        sub inc { $_++ for @_ }
        inc(($a,$b,$c,$d) = (10,20))
    
    The four scalar vars will end up with the values (11,21,1,1), with
    the list assign op returning 4 lval scalars to be passed to inc.
    
    However, when the LHS includes a hash or array, any 'empty' scalars weren't
    being returned. This:
    
        inc(($a,$b,@array,$c) = (10))
    
    used to leave $b and $c undefined. With this commit, they are both set to
    1.
    
    This change broke some tests in hashassign.t, which were added in 2012
    by commit v5.17.6-295-gb1babc5. Half these tests were commented as
    
        # this arguable, but this is how it works
    
    and they all tested that a scalar following a hash wasn';t returned in
    lvalue context; i.e. they assumes that
    
        inc((%h, $x) = (...))
    
    *wouldn't* increment $x. This commit causes $x to be incremented, and
    I've changed the failing tests.
    
    It also adds an EXTEND(SP,1) in the scalar case; otherwise for
    scalar(() = @a) with empty @a, it could in theory push the '0' return
    value off the end of the stack.


diff --git a/t/op/aassign.t b/t/op/aassign.t
index 063c5a1..6d0a3a4 100644
--- a/t/op/aassign.t
+++ b/t/op/aassign.t
@@ -482,4 +482,75 @@ SKIP: {
     is(join(' ', sort values %a), "v1 v2", "double hash non-empty A values");
 }
 
+#  list and lval context: filling of missing elements, returning correct
+#  lvalues.
+#  ( Note that these partially duplicate some tests in hashassign.t which
+#  I didn't spot at first - DAPM)
+
+{
+    my ($x, $y, $z);
+    my (@a, %h);
+
+    sub lval {
+        my $n    = shift;
+        my $desc = shift;
+        is($x, $n >= 1 ? "assign1" : undef, "lval: X pre $n $desc");
+        is($y, $n >= 2 ? "assign2" : undef, "lval: Y pre $n $desc");
+        is($z,                       undef, "lval: Z pre $n $desc");
+
+        my $i = 0;
+        for (@_) {
+            $_ = "lval$i";
+            $i++;
+        }
+        is($x, "lval0", "lval: a post $n $desc");
+        is($y, "lval1", "lval: b post $n $desc");
+        is($z, "lval2", "lval: c post $n $desc");
+    }
+    lval(0, "XYZ", (($x,$y,$z) = ()));
+    lval(1, "XYZ", (($x,$y,$z) = (qw(assign1))));
+    lval(2, "XYZ", (($x,$y,$z) = (qw(assign1 assign2))));
+
+    lval(0, "XYZA", (($x,$y,$z,@a) = ()));
+    lval(1, "XYZA", (($x,$y,$z,@a) = (qw(assign1))));
+    lval(2, "XYZA", (($x,$y,$z,@a) = (qw(assign1 assign2))));
+
+    lval(0, "XYAZ", (($x,$y,@a,$z) = ()));
+    lval(1, "XYAZ", (($x,$y,@a,$z) = (qw(assign1))));
+    lval(2, "XYAZ", (($x,$y,@a,$z) = (qw(assign1 assign2))));
+
+    lval(0, "XYZH", (($x,$y,$z,%h) = ()));
+    lval(1, "XYZH", (($x,$y,$z,%h) = (qw(assign1))));
+    lval(2, "XYZH", (($x,$y,$z,%h) = (qw(assign1 assign2))));
+
+    lval(0, "XYHZ", (($x,$y,%h,$z) = ()));
+    lval(1, "XYHZ", (($x,$y,%h,$z) = (qw(assign1))));
+    lval(2, "XYHZ", (($x,$y,%h,$z) = (qw(assign1 assign2))));
+
+    # odd number of hash elements
+
+    {
+        no warnings 'misc';
+        @a = ((%h) = qw(X));
+        is (join(":", map $_ // "u", @a), "X:u",      "lval odd singleton");
+        @a = (($x, $y, %h) = qw(X Y K));
+        is (join(":", map $_ // "u", @a), "X:Y:K:u",   "lval odd");
+        @a = (($x, $y, %h, $z) = qw(X Y K));
+        is (join(":", map $_ // "u", @a), "X:Y:K:u:u", "lval odd with z");
+    }
+
+    # undef on LHS uses RHS as lvalue instead
+    # Note this this just codifies existing behaviour - it may not be
+    # correct. See http://nntp.perl.org/group/perl.perl5.porters/240358.
+
+    {
+        ($x, $y, $z)  = (0, 10, 20);
+        $_++ for ((undef, $x) = ($y, $z));
+        is "$x:$y:$z", "21:11:20", "undef as lvalue";
+    }
+
+}




ok 83 - lval: X pre 0 XYZ
ok 84 - lval: Y pre 0 XYZ
ok 85 - lval: Z pre 0 XYZ
ok 86 - lval: a post 0 XYZ
ok 87 - lval: b post 0 XYZ
ok 88 - lval: c post 0 XYZ
ok 89 - lval: X pre 1 XYZ
ok 90 - lval: Y pre 1 XYZ
ok 91 - lval: Z pre 1 XYZ
ok 92 - lval: a post 1 XYZ
ok 93 - lval: b post 1 XYZ
ok 94 - lval: c post 1 XYZ
ok 95 - lval: X pre 2 XYZ
ok 96 - lval: Y pre 2 XYZ
ok 97 - lval: Z pre 2 XYZ
ok 98 - lval: a post 2 XYZ
ok 99 - lval: b post 2 XYZ
ok 100 - lval: c post 2 XYZ
ok 101 - lval: X pre 0 XYZA
ok 102 - lval: Y pre 0 XYZA
ok 103 - lval: Z pre 0 XYZA
not ok 104 - lval: a post 0 XYZA
# Failed test 104 - lval: a post 0 XYZA at t/op/aassign.t line 504
#      got undef
# expected "lval0"
not ok 105 - lval: b post 0 XYZA
# Failed test 105 - lval: b post 0 XYZA at t/op/aassign.t line 505
#      got undef
# expected "lval1"
not ok 106 - lval: c post 0 XYZA
# Failed test 106 - lval: c post 0 XYZA at t/op/aassign.t line 506
#      got undef
# expected "lval2"
ok 107 - lval: X pre 1 XYZA
ok 108 - lval: Y pre 1 XYZA
ok 109 - lval: Z pre 1 XYZA
ok 110 - lval: a post 1 XYZA
not ok 111 - lval: b post 1 XYZA
# Failed test 111 - lval: b post 1 XYZA at t/op/aassign.t line 505
#      got undef
# expected "lval1"
not ok 112 - lval: c post 1 XYZA
# Failed test 112 - lval: c post 1 XYZA at t/op/aassign.t line 506
#      got undef
# expected "lval2"
ok 113 - lval: X pre 2 XYZA
ok 114 - lval: Y pre 2 XYZA
ok 115 - lval: Z pre 2 XYZA
ok 116 - lval: a post 2 XYZA
ok 117 - lval: b post 2 XYZA
not ok 118 - lval: c post 2 XYZA
# Failed test 118 - lval: c post 2 XYZA at t/op/aassign.t line 506
#      got undef
# expected "lval2"
ok 119 - lval: X pre 0 XYAZ
ok 120 - lval: Y pre 0 XYAZ
ok 121 - lval: Z pre 0 XYAZ
not ok 122 - lval: a post 0 XYAZ
# Failed test 122 - lval: a post 0 XYAZ at t/op/aassign.t line 504
#      got undef
# expected "lval0"
not ok 123 - lval: b post 0 XYAZ
# Failed test 123 - lval: b post 0 XYAZ at t/op/aassign.t line 505
#      got undef
# expected "lval1"
not ok 124 - lval: c post 0 XYAZ
# Failed test 124 - lval: c post 0 XYAZ at t/op/aassign.t line 506
#      got undef
# expected "lval2"
ok 125 - lval: X pre 1 XYAZ
ok 126 - lval: Y pre 1 XYAZ
ok 127 - lval: Z pre 1 XYAZ
ok 128 - lval: a post 1 XYAZ
not ok 129 - lval: b post 1 XYAZ
# Failed test 129 - lval: b post 1 XYAZ at t/op/aassign.t line 505
#      got undef
# expected "lval1"
not ok 130 - lval: c post 1 XYAZ
# Failed test 130 - lval: c post 1 XYAZ at t/op/aassign.t line 506
#      got undef
# expected "lval2"
ok 131 - lval: X pre 2 XYAZ
ok 132 - lval: Y pre 2 XYAZ
ok 133 - lval: Z pre 2 XYAZ
ok 134 - lval: a post 2 XYAZ
ok 135 - lval: b post 2 XYAZ
not ok 136 - lval: c post 2 XYAZ
# Failed test 136 - lval: c post 2 XYAZ at t/op/aassign.t line 506
#      got undef
# expected "lval2"
ok 137 - lval: X pre 0 XYZH
ok 138 - lval: Y pre 0 XYZH
ok 139 - lval: Z pre 0 XYZH
not ok 140 - lval: a post 0 XYZH
# Failed test 140 - lval: a post 0 XYZH at t/op/aassign.t line 504
#      got undef
# expected "lval0"
not ok 141 - lval: b post 0 XYZH
# Failed test 141 - lval: b post 0 XYZH at t/op/aassign.t line 505
#      got undef
# expected "lval1"
not ok 142 - lval: c post 0 XYZH
# Failed test 142 - lval: c post 0 XYZH at t/op/aassign.t line 506
#      got undef
# expected "lval2"
ok 143 - lval: X pre 1 XYZH
ok 144 - lval: Y pre 1 XYZH
ok 145 - lval: Z pre 1 XYZH
ok 146 - lval: a post 1 XYZH
not ok 147 - lval: b post 1 XYZH
# Failed test 147 - lval: b post 1 XYZH at t/op/aassign.t line 505
#      got undef
# expected "lval1"
not ok 148 - lval: c post 1 XYZH
# Failed test 148 - lval: c post 1 XYZH at t/op/aassign.t line 506
#      got undef
# expected "lval2"
ok 149 - lval: X pre 2 XYZH
ok 150 - lval: Y pre 2 XYZH
ok 151 - lval: Z pre 2 XYZH
ok 152 - lval: a post 2 XYZH
ok 153 - lval: b post 2 XYZH
not ok 154 - lval: c post 2 XYZH
# Failed test 154 - lval: c post 2 XYZH at t/op/aassign.t line 506
#      got undef
# expected "lval2"
ok 155 - lval: X pre 0 XYHZ
ok 156 - lval: Y pre 0 XYHZ
ok 157 - lval: Z pre 0 XYHZ
not ok 158 - lval: a post 0 XYHZ
# Failed test 158 - lval: a post 0 XYHZ at t/op/aassign.t line 504
#      got undef
# expected "lval0"
not ok 159 - lval: b post 0 XYHZ
# Failed test 159 - lval: b post 0 XYHZ at t/op/aassign.t line 505
#      got undef
# expected "lval1"
not ok 160 - lval: c post 0 XYHZ
# Failed test 160 - lval: c post 0 XYHZ at t/op/aassign.t line 506
#      got undef
# expected "lval2"
ok 161 - lval: X pre 1 XYHZ
ok 162 - lval: Y pre 1 XYHZ
ok 163 - lval: Z pre 1 XYHZ
ok 164 - lval: a post 1 XYHZ
not ok 165 - lval: b post 1 XYHZ
# Failed test 165 - lval: b post 1 XYHZ at t/op/aassign.t line 505
#      got undef
# expected "lval1"
not ok 166 - lval: c post 1 XYHZ
# Failed test 166 - lval: c post 1 XYHZ at t/op/aassign.t line 506
#      got undef
# expected "lval2"
ok 167 - lval: X pre 2 XYHZ
ok 168 - lval: Y pre 2 XYHZ
ok 169 - lval: Z pre 2 XYHZ
ok 170 - lval: a post 2 XYHZ
ok 171 - lval: b post 2 XYHZ
not ok 172 - lval: c post 2 XYHZ
# Failed test 172 - lval: c post 2 XYHZ at t/op/aassign.t line 506
#      got undef
# expected "lval2"
ok 173 - lval odd
not ok 174 - lval odd with z
# Failed test 174 - lval odd with z at t/op/aassign.t line 535
#      got "X:Y:K:u"
# expected "X:Y:K:u:u"
ok 175 - undef as lvalue
1..175



-- 
"Strange women lying in ponds distributing swords is no basis for a system
of government. Supreme executive power derives from a mandate from the
masses, not from some farcical aquatic ceremony."
    -- Dennis, "Monty Python and the Holy Grail"

Thread Previous | Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About