Perl / perl5

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

stringify overloads forget that vstrings are vstrings #22133

Open leonerd opened 3 weeks ago

leonerd commented 3 weeks ago

If an object class provides overloaded stringification, and you attempt to return a vstring as the result of that stringify, perl will forget that the vstring is a vstring and retain only its "plain" PV representation:

A vstring should look like:

use v5.36;
use Devel::Peek;
use Scalar::Util 'isvstring';

my $vstr = v48.49.50;
say "vstr = $vstr";
Dump($vstr);
say "vstr " . (isvstring($vstr)?"is":"is not") . " a vstring";
vstr = 012
SV = PVMG(0x560b97f818a0) at 0x560b97f3ead0
  REFCNT = 2
  FLAGS = (RMG,POK,IsCOW,pPOK)
  IV = 0
  NV = 0
  PV = 0x560b97f48850 "012"\0
  CUR = 3
  LEN = 16
  COW_REFCNT = 1
  MAGIC = 0x560b97f52b80
    MG_VIRTUAL = 0
    MG_TYPE = PERL_MAGIC_vstring(V)
    MG_LEN = 9
    MG_PTR = 0x560b97f46c80 "v48.49.50"
vstr is a vstring

However, sending the result via overloaded stringify forgets this fact:

use v5.36;
use Devel::Peek;
use Scalar::Util 'isvstring';

my $vstr = v48.49.50;
package SomeClass {
  use overload '""' => sub { $vstr; },
}

my $obj = bless [], "SomeClass";
my $result = "$obj";
say "result = $result";
Dump($result);
say "result " . (isvstring($result)?"is":"is not") . " a vstring";
result = 012
SV = PV(0x56091d29af70) at 0x56091d360028
  REFCNT = 1
  FLAGS = (POK,pPOK)
  PV = 0x56091d3914f0 "012"\0
  CUR = 3
  LEN = 16
result is not a vstring
leonerd commented 3 weeks ago

The explanation for this is that, unlike most "weird kinds of values", vstrings are magicalised PVs themselves, rather than object refs. The value is directly an SV containing a PV string, which additionally has the PERL_MAGIC_vstring magic applied to.

The Perl_sv_setsv() function makes a special-case to handle copying that magic around the place, which is why my $vstr = v... actually works. However, in the implementation of overloaded stringification there is no place in which to copy that value around. The tmpstr that overloaded stringifcation obtains in Perl_sv_2pv_flags() contains that magic annotation but it is not possible to copy it "out of" that function and into the caller, because the caller invoked some variant of the SvPV macro in order to directly obtain the char * / STRLEN pair of the underlying string buffer. This forgot that it was a vstring.

exercism-1 commented 3 weeks ago

Isn't that the expected behavior?

$ perl -wE 'use Scalar::Util qw(isvstring); my $vstr = v48.49.50; say isvstring("$vstr") ? "yes" : "no"'
no

The result of stringifying a v-string is a plain string. The result of stringifying an object (overloaded or not) should also be a plain string.

haarg commented 2 weeks ago

We may want to consider removing vstring magic. I don't think it is necessary anymore for version.pm to work correctly. And if version.pm does still need it, we should look at fixing that.

mauke commented 2 weeks ago

I thought the problem was without magic you can't reliably distinguish between v50.46.49 and 2.1. Has that changed?

haarg commented 2 weeks ago

The original purpose of vstring magic was to maintain the exact initial formatting of vstrings, including underscores. Because version.pm wanted to treat underscores as a special case of a ..