Perl / perl5

🐪 The Perl programming language
https://dev.perl.org/perl5/
Other
1.97k stars 559 forks source link

Hash slice value modifications using transformative assignment operators only modify the value of the last sliced key #21033

Open teodesian opened 1 year ago

teodesian commented 1 year ago

Description Hash slice value modifications with transformative assignment operators (+=, -=, *=, /=,) only modify the value of the last sliced key.

Concatenative and logic assignment (.=, &=, etc) also appear to exhibit this behavior. I suspect regex comparisons do as well, but have not checked this.

What is most concerning to me is that undefined-or and or-assignment (//=, ||=) also fail in this fashion. This could allow much more flexibility/simplicity in performing hash merges if it modified all the relevant values.

I assume the cause is similar to that of #20537, but filing separately in case this is not true. Given the oddly specific actual behavior here, I suspect something is relying on this behavior in non-obvious ways.

I discovered this while attempting to List::Util::reduce an array of hashes which had a number of keys whos values needed summing. Slice assign seemed the most straightforward way to do this.

Steps to Reproduce

perl -MData::Dumper -e 'my %slicer = (a => 1, b => 1); my %sliced = ( a => 2, b => 4 ); @slicer{qw{a b}} += @sliced{qw{a b}}; print Dumper(\%slicer);'

Actual behavior

$VAR1 = {
          'a' => 1,
          'b' => 5
        };

Expected behavior

$VAR1 = {
          'a' => 3,
          'b' => 5
        };

Perl configuration

Summary of my perl5 (revision 5 version 32 subversion 0) configuration:

  Platform:
    osname=linux
    osvers=3.10.0-1160.31.1.el7.x86_64
    archname=x86_64-linux
    uname='linux eloquent-tu.209-172-38-37.plesk.page 3.10.0-1160.31.1.el7.x86_64 #1 smp thu jun 10 13:32:12 utc 2021 x86_64 x86_64 x86_64 gnulinux '
    config_args='-des -Dprefix=/perls/perl5.32.0'
    hint=recommended
    useposix=true
    d_sigaction=define
    useithreads=undef
    usemultiplicity=undef
    use64bitint=define
    use64bitall=define
    uselongdouble=undef
    usemymalloc=n
    default_inc_excludes_dot=define
    bincompat5005=undef
  Compiler:
    cc='cc'
    ccflags ='-fwrapv -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2'
    optimize='-O2'
    cppflags='-fwrapv -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include'
    ccversion=''
    gccversion='4.8.5 20150623 (Red Hat 4.8.5-44)'
    gccosandvers=''
    intsize=4
    longsize=8
    ptrsize=8
    doublesize=8
    byteorder=12345678
    doublekind=3
    d_longlong=define
    longlongsize=8
    d_longdbl=define
    longdblsize=16
    longdblkind=3
    ivtype='long'
    ivsize=8
    nvtype='double'
    nvsize=8
    Off_t='off_t'
    lseeksize=8
    alignbytes=8
    prototype=define
  Linker and Libraries:
    ld='cc'
    ldflags =' -fstack-protector-strong -L/usr/local/lib'
    libpth=/usr/local/lib /usr/lib /lib/../lib64 /usr/lib/../lib64 /lib /lib64 /usr/lib64 /usr/local/lib64
    libs=-lpthread -lnsl -ldl -lm -lcrypt -lutil -lc
    perllibs=-lpthread -lnsl -ldl -lm -lcrypt -lutil -lc
    libc=libc-2.17.so
    so=so
    useshrplib=false
    libperl=libperl.a
    gnulibc_version='2.17'
  Dynamic Linking:
    dlsrc=dl_dlopen.xs
    dlext=so
    d_dlsymun=undef
    ccdlflags='-Wl,-E'
    cccdlflags='-fPIC'
    lddlflags='-shared -O2 -L/usr/local/lib -fstack-protector-strong'

Characteristics of this binary (from libperl):
  Compile-time options:
    HAS_TIMES
    PERLIO_LAYERS
    PERL_COPY_ON_WRITE
    PERL_DONT_CREATE_GVSV
    PERL_MALLOC_WRAP
    PERL_OP_PARENT
    PERL_PRESERVE_IVUV
    USE_64_BIT_ALL
    USE_64_BIT_INT
    USE_LARGE_FILES
    USE_LOCALE
    USE_LOCALE_COLLATE
    USE_LOCALE_CTYPE
    USE_LOCALE_NUMERIC
    USE_LOCALE_TIME
    USE_PERLIO
    USE_PERL_ATOF
  Built under linux
  Compiled at Jul  9 2021 21:11:25
  @INC:
    /perls/perl5.32.0/lib/site_perl/5.32.0/x86_64-linux
    /perls/perl5.32.0/lib/site_perl/5.32.0
    /perls/perl5.32.0/lib/5.32.0/x86_64-linux
    /perls/perl5.32.0/lib/5.32.0
tonycoz commented 1 year ago

The base operators for each of the assignment operators you've mentioned are all scalar operators - they don't operate on lists, so both the assigned to slice and the right argument are evaluated in scalar context.

This would be a new feature rather than a bug fix.