http://qs321.pair.com?node_id=11118049

pl_pm has asked for the wisdom of the Perl Monks concerning the following question:

Hi all,

I have upgraded Perl from v5.24 to v5.30.3 and my script does not work now. The problem is in ${^POSTMATCH}, the following simple script illustrates it:

use warnings; use strict; use v5.10; sub add_incr_suffix { state $suffix = 'A'; return "prefix-TEXT-" . $suffix++; } print 'Test ' . ( add_incr_suffix =~ /^prefix-*/p ? ${^POSTMATCH} : '' ) . "," . ( add_incr_suffix =~ /^prefix-*/p ? ${^POSTMATCH} : '' ) . "," . ( add_incr_suffix =~ /^prefix-*/p ? ${^POSTMATCH} : '' ) . "\n";

Perl v5.24.0 (and v5.26.1] returns the desired result Test TEXT-A,TEXT-B,TEXT-C but both v5.28.0 and v5.30.3 return Test TEXT-C,TEXT-C,TEXT-C.

If I replace string concatenation with join() in the print() statement, all above mentioned versions return Test TEXT-C,TEXT-C,TEXT-C.

Is it a bug or my use case is wrong? Many thanks for your opinion.

Best regards

Replies are listed 'Best First'.
Re: ${^POSTMATCH} problem
by tybalt89 (Monsignor) on Jun 14, 2020 at 13:58 UTC

    It has nothing specifically to do with POSTMATCH, you just ran into the multi-concatenation optimization.

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11118049 use warnings; use v5.10; sub add_incr_suffix { state $suffix = 'A'; return "prefix-TEXT-" . $suffix++; } print 'Test ' . ( add_incr_suffix =~ /^prefix-*/p ? "${^POSTMATCH}" : '' ) . "," . ( add_incr_suffix =~ /^prefix-*/p ? "${^POSTMATCH}" : '' ) . "," . ( add_incr_suffix =~ /^prefix-*/p ? "${^POSTMATCH}" : '' ) . "\n";

    Here is a simpler example that produces different output under 5.26 and 5.30

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11118049 use warnings; my $foo; print "test: " . ($foo = 1) . ($foo = 2) . ($foo = 3), "\n";
      Good catch!

      > It has nothing specifically to do with POSTMATCH,

      I think the crucial point is that a $variable is passed by reference (read alias)

      Compare

      DB<25> sub tt { print "@_"} DB<26> tt ( ($foo = 1) , ($foo = 2) , ($foo = 3) ) 3 3 3 DB<27>

      So the implementation of concat was changed in a function way...

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        Yeah, The behaviour of ($foo = 1) . ($foo = 2) . ($foo = 3) used to be roughly equivalent to

        my $x = ""; $x .= ( $foo = 1 ); $x .= ( $foo = 2 ); $x .= ( $foo = 3 );

        But now, it's roughly equivalent to

        my $x = join("", ( $foo = 1 ), ( $foo = 2 ), ( $foo = 3 ), );

        In both cases, $foo itself is being put on the stack. The difference is when it's used (before it has a chance to be changed by later assignments, or after).

Re: ${^POSTMATCH} problem
by Haarg (Priest) on Jun 14, 2020 at 14:09 UTC

    Internally, the expression is essentially saving a reference to ${^POSTMATCH}, which it will fetch from later when it does the concatenation. Depending on the order that the concatenation is performed in, this can lead to varied results. Small changes in implementation can impact this, but most likely this changed due to the introduction of the multiconcat op.

    You can force early fetching of the ${^POSTMATCH} variable by putting it in double quotes. This can be a good practice in other places when using regular expression globals, such as when passing match variables ("$1", "$2") to functions.

      This effect is well known with function arguments and any repeated variables , but IMHO not common with built-in operators.°

      I consider this an implementation glitch which should be fixed if possible.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

      °) some take lvalues like substr but there is no reason to consider concat alike

      Update

      Changed splice to substr

      I see, thank you for the explanation.

      So, in this case, the point is that if I “wrap” scalar into an expression (stringification, concat wish an empty string,...), the multiconcat will use the result of the expression instead of a reference. It works well, thank you. However, I have used an array and join(map()) to fix the script.

      Thank you all.

Re: ${^POSTMATCH} problem
by LanX (Saint) on Jun 14, 2020 at 13:59 UTC
    I can't run many tests right now, but what you describe sounds like a side effect / evaluation order problem with using this special variable multiple times inside the same statement.

    (Similar with using func($x++,$x--) where the evaluation order is undefined and aliases are passed)

    Splitting the statement into multiple ones with just one postmatch should fix it.

    I'll try later...

    Update

    See Re^2: ${^POSTMATCH} problem

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

Re: ${^POSTMATCH} problem
by perlfan (Vicar) on Jun 14, 2020 at 14:08 UTC
    I believe you're running into the "This variable is read-only and dynamically-scoped." aspect of this variable actually working; I take that to mean once it is set in the same scope, it can't be overwritten. Not sure if this a bug or a bug had been fix, either case would explain the discrepency you're claiming.

    The following code (perl v5.28.2) seems to do what you want; of course the matches are no longer in the same dynamic scope since the concatentation over all strings is no longer being utilized.

    use warnings; use strict; use v5.10; sub add_incr_suffix { state $suffix = 'A'; return "prefix-TEXT-" . $suffix++; } print "Test \n"; print add_incr_suffix =~ /^prefix-*/p ? ${^POSTMATCH} : '', ", "; print add_incr_suffix =~ /^prefix-*/p ? ${^POSTMATCH} : '', ", "; print add_incr_suffix =~ /^prefix-*/p ? ${^POSTMATCH} : '', "\n";
    Outputs:
    Test TEXT-A, TEXT-B, TEXT-C

    In anycase, seems like you may have been depending on a bug for some behavior that is no longer present.