Revision history for Variable-Magic
+0.10 2008-02-04 11:30 UTC
+ + Add : New script : samples/vm_vs_tie.pl, that benchmarks our uvar
+ magic versus tied hashes.
+ + Add : The VMG_COMPAT_* constants can be used from userspace to check
+ perl magic abilities.
+ + Fix : Callbacks that returned undef made us croak, breaking the
+ variable behaviour (regression test in 14-callbacks.t).
+ + Fix : uvar callbacks weren't tested for non-NULL-ity before being
+ called (regression test in 28-uvar.t).
+ + Tst : Fix typo in 25-copy.t that prevented Tie::Hash tests to be ran.
+
0.09 2008-02-02 11:30 UTC
+ Doc : Explicitely say that uvar callbacks are safely ignored for
non-hashes.
lib/Variable/Magic.pm
samples/magic.pl
samples/uvar.pl
+samples/vm_vs_tie.pl
t/00-load.t
t/01-import.t
t/10-simple.t
t/11-multiple.t
-t/12-data.t
-t/13-sig.t
-t/14-self.t
-t/15-huf.t
+t/12-sig.t
+t/13-data.t
+t/14-callbacks.t
+t/15-self.t
+t/16-huf.t
t/20-get.t
t/21-set.t
t/22-len.t
--- #YAML:1.0
name: Variable-Magic
-version: 0.09
+version: 0.10
abstract: Associate user-defined magic to variables from Perl.
license: perl
author:
#define R(S) fprintf(stderr, "R(" #S ") = %d\n", SvREFCNT(S))
+#define PERL_VERSION_GE(R, V, S) (PERL_REVISION > (R) || (PERL_REVISION == (R) && (PERL_VERSION > (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION >= (S))))))
+
#define PERL_VERSION_LE(R, V, S) (PERL_REVISION < (R) || (PERL_REVISION == (R) && (PERL_VERSION < (V) || (PERL_VERSION == (V) && (PERL_SUBVERSION <= (S))))))
#define PERL_API_VERSION_GE(R, V, S) (PERL_API_REVISION > (R) || (PERL_API_REVISION == (R) && (PERL_API_VERSION > (V) || (PERL_API_VERSION == (V) && (PERL_API_SUBVERSION >= (S))))))
+#define PERL_API_VERSION_LE(R, V, S) (PERL_API_REVISION < (R) || (PERL_API_REVISION == (R) && (PERL_API_VERSION < (V) || (PERL_API_VERSION == (V) && (PERL_API_SUBVERSION <= (S))))))
+
/* --- Compatibility ------------------------------------------------------- */
#ifndef Newx
# define VMG_UVAR 0
#endif
+#if PERL_VERSION_GE(5, 9, 3)
+# define VMG_COMPAT_ARRAY_PUSH_NOLEN 1
+#else
+# define VMG_COMPAT_ARRAY_PUSH_NOLEN 0
+#endif
+
+/* since 5.9.5 - see #43357 */
+#if PERL_VERSION_GE(5, 9, 5)
+# define VMG_COMPAT_ARRAY_UNDEF_CLEAR 1
+#else
+# define VMG_COMPAT_ARRAY_UNDEF_CLEAR 0
+#endif
+
+#if MGf_COPY && PERL_VERSION_GE(5, 9, 4)
+# define VMG_COMPAT_HASH_LISTASSIGN_COPY 1
+#else
+# define VMG_COMPAT_HASH_LISTASSIGN_COPY 0
+#endif
+
#if VMG_UVAR
/* Bug-free mg_magical - see http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/2008-01/msg00036.html */
/* --- Signatures ---------------------------------------------------------- */
#define SIG_MIN ((U16) (1u << 8))
-#define SIG_MAX ((U16) (1u << 16 - 1))
+#define SIG_MAX ((U16) ((1u << 16) - 1))
#define SIG_NBR (SIG_MAX - SIG_MIN + 1)
-#define SIG_WIZ ((U16) (1u << 8 - 1))
+#define SIG_WIZ ((U16) ((1u << 8) - 1))
/* ... Generate signatures ................................................. */
STATIC SV *vmg_data_get(SV *sv, U16 sig) {
MAGIC *mg, *moremagic;
- MGWIZ *w;
if (SvTYPE(sv) >= SVt_PVMG) {
for (mg = SvMAGIC(sv); mg; mg = moremagic) {
STATIC int vmg_cb_call(pTHX_ SV *cb, SV *sv, SV *data) {
#define vmg_cb_call(I, S, D) vmg_cb_call(aTHX_ (I), (S), (D))
+ SV *svr;
int ret;
dSP;
SPAGAIN;
if (count != 1) { croak("Callback needs to return 1 scalar\n"); }
- ret = POPi;
+ svr = POPs;
+ ret = SvOK(svr) ? SvIV(svr) : 0;
PUTBACK;
#if MGf_COPY || VMG_UVAR
STATIC int vmg_cb_call2(pTHX_ SV *cb, SV *sv, SV *data, SV *sv2) {
#define vmg_cb_call2(I, S, D, S2) vmg_cb_call2(aTHX_ (I), (S), (D), (S2))
+ SV *svr;
int ret;
dSP;
SPAGAIN;
if (count != 1) { croak("Callback needs to return 1 scalar\n"); }
- ret = POPi;
+ svr = POPs;
+ ret = SvOK(svr) ? SvIV(svr) : 0;
PUTBACK;
}
STATIC U32 vmg_svt_len(pTHX_ SV *sv, MAGIC *mg) {
+ SV *svr;
+ I32 len;
U32 ret;
dSP;
XPUSHs(sv_2mortal(newRV_inc(sv)));
XPUSHs(mg->mg_obj ? mg->mg_obj : &PL_sv_undef);
if (SvTYPE(sv) == SVt_PVAV) {
- XPUSHs(sv_2mortal(newSViv(av_len((AV *) sv) + 1)));
+ len = av_len((AV *) sv) + 1;
+ XPUSHs(sv_2mortal(newSViv(len)));
}
PUTBACK;
SPAGAIN;
if (count != 1) { croak("Callback needs to return 1 scalar\n"); }
- ret = POPi;
+ svr = POPs;
+ ret = SvOK(svr) ? SvUV(svr)
+ : ((SvTYPE(sv) == SVt_PVAV) ? len : 1);
PUTBACK;
|| (mg->mg_private < SIG_MIN)
|| (mg->mg_private > SIG_MAX)) { continue; }
w = SV2MGWIZ(mg->mg_ptr);
+ if (!w->uvar) { continue; }
switch (action) {
case 0:
- vmg_cb_call2(w->cb_fetch, sv, mg->mg_obj, key);
+ if (w->cb_fetch) { vmg_cb_call2(w->cb_fetch, sv, mg->mg_obj, key); }
break;
case HV_FETCH_ISSTORE:
case HV_FETCH_LVALUE:
case (HV_FETCH_ISSTORE|HV_FETCH_LVALUE):
- vmg_cb_call2(w->cb_store, sv, mg->mg_obj, key);
+ if (w->cb_store) { vmg_cb_call2(w->cb_store, sv, mg->mg_obj, key); }
break;
case HV_FETCH_ISEXISTS:
- vmg_cb_call2(w->cb_exists, sv, mg->mg_obj, key);
+ if (w->cb_exists) { vmg_cb_call2(w->cb_exists, sv, mg->mg_obj, key); }
break;
case HV_DELETE:
- vmg_cb_call2(w->cb_delete, sv, mg->mg_obj, key);
+ if (w->cb_delete) { vmg_cb_call2(w->cb_delete, sv, mg->mg_obj, key); }
break;
}
}
#endif /* MGf_COPY */
#if MGf_DUP
if (w->cb_dup != NULL) { SvREFCNT_dec(SvRV(w->cb_dup)); }
-#endif /* MGf_COPY */
+#endif /* MGf_DUP */
#if MGf_LOCAL
if (w->cb_local != NULL) { SvREFCNT_dec(SvRV(w->cb_local)); }
-#endif /* MGf_COPY */
+#endif /* MGf_LOCAL */
#if VMG_UVAR
if (w->cb_fetch != NULL) { SvREFCNT_dec(SvRV(w->cb_fetch)); }
if (w->cb_store != NULL) { SvREFCNT_dec(SvRV(w->cb_store)); }
#endif /* MGf_DUP */
#if MGf_LOCAL
NULL, /* local */
-#endif /* MGf_DUP */
+#endif /* MGf_LOCAL */
};
STATIC const char vmg_invalid_wiz[] = "Invalid wizard object";
newCONSTSUB(stash, "MGf_DUP", newSVuv(MGf_DUP));
newCONSTSUB(stash, "MGf_LOCAL", newSVuv(MGf_LOCAL));
newCONSTSUB(stash, "VMG_UVAR", newSVuv(VMG_UVAR));
+ newCONSTSUB(stash, "VMG_COMPAT_ARRAY_PUSH_NOLEN",
+ newSVuv(VMG_COMPAT_ARRAY_PUSH_NOLEN));
+ newCONSTSUB(stash, "VMG_COMPAT_ARRAY_UNDEF_CLEAR",
+ newSVuv(VMG_COMPAT_ARRAY_UNDEF_CLEAR));
+ newCONSTSUB(stash, "VMG_COMPAT_HASH_LISTASSIGN_COPY",
+ newSVuv(VMG_COMPAT_HASH_LISTASSIGN_COPY));
}
SV *_wizard(...)
if (SvOK(svsig)) {
SV **old;
sig = vmg_sv2sig(svsig);
- if (old = hv_fetch(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), 0)) {
+ if ((old = hv_fetch(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), 0))) {
ST(0) = sv_2mortal(newRV_inc(*old));
XSRETURN(1);
}
char buf[8];
SV **old;
U16 sig = vmg_sv2sig(wiz);
- if (old = hv_fetch(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), 0)) {
+ if ((old = hv_fetch(MY_CXT.wizz, buf, sprintf(buf, "%u", sig), 0))) {
wiz = *old;
} else {
XSRETURN_UNDEF;
Variable::Magic - Associate user-defined magic to variables from Perl.
VERSION
- Version 0.09
+ Version 0.10
SYNOPSIS
use Variable::Magic qw/wizard cast dispell/;
When this constant is true, you can use the "fetch,store,exists,delete"
callbacks on hashes.
+ "VMG_COMPAT_ARRAY_PUSH_NOLEN"
+ True for perls that don't call 'len' magic when you push an element in a
+ magical array.
+
+ "VMG_COMPAT_ARRAY_UNDEF_CLEAR"
+ True for perls that call 'clear' magic when undefining magical arrays.
+
+ "VMG_COMPAT_HASH_LISTASSIGN_COPY"
+ True for perls that call 'copy' magic on list assignments. Implies that
+ "MGf_COPY" is true.
+
FUNCTIONS
"wizard"
wizard sig => ...,
=head1 VERSION
-Version 0.09
+Version 0.10
=cut
our $VERSION;
BEGIN {
- $VERSION = '0.09';
+ $VERSION = '0.10';
}
=head1 SYNOPSIS
When this constant is true, you can use the C<fetch,store,exists,delete> callbacks on hashes.
+=head2 C<VMG_COMPAT_ARRAY_PUSH_NOLEN>
+
+True for perls that don't call 'len' magic when you push an element in a magical array.
+
+=head2 C<VMG_COMPAT_ARRAY_UNDEF_CLEAR>
+
+True for perls that call 'clear' magic when undefining magical arrays.
+
+=head2 C<VMG_COMPAT_HASH_LISTASSIGN_COPY>
+
+True for perls that call 'copy' magic on list assignments. Implies that C<MGf_COPY> is true.
+
=head1 FUNCTIONS
=cut
our @EXPORT = ();
our %EXPORT_TAGS = (
'funcs' => [ qw/wizard gensig getsig cast getdata dispell/ ],
- 'consts' => [ qw/SIG_MIN SIG_MAX SIG_NBR MGf_COPY MGf_DUP MGf_LOCAL VMG_UVAR/ ]
+ 'consts' => [ qw/SIG_MIN SIG_MAX SIG_NBR MGf_COPY MGf_DUP MGf_LOCAL VMG_UVAR/,
+ qw/VMG_COMPAT_ARRAY_PUSH_NOLEN VMG_COMPAT_ARRAY_UNDEF_CLEAR VMG_COMPAT_HASH_LISTASSIGN_COPY/ ]
);
our @EXPORT_OK = map { @$_ } values %EXPORT_TAGS;
$EXPORT_TAGS{'all'} = [ @EXPORT_OK ];
-#!/usr/bin/perl
+#!/usr/bin/env perl
use strict;
use warnings;
-#!/usr/bin/perl
+#!/usr/bin/env perl
use strict;
use warnings;
--- /dev/null
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use Tie::Hash;
+
+use lib qw{blib/arch blib/lib};
+use Variable::Magic qw/wizard cast VMG_UVAR/;
+
+use Benchmark qw/cmpthese/;
+
+die 'Your perl does not support the nice uvar magic of 5.10.*' unless VMG_UVAR;
+
+my @a = ('a' .. 'z');
+
+tie my %t, 'Tie::StdHash';
+$t{$a[$_]} = $_ for 0 .. $#a;
+
+my $wiz = wizard fetch => sub { 0 }, store => sub { 0 };
+my %v;
+$v{$a[$_]} = $_ for 0 .. $#a;
+cast %v, $wiz;
+
+cmpthese -3, {
+ 'tie' => sub { my ($x, $y) = map @a[rand @a], 1 .. 2; my $a = $t{$x}; $t{$y} = $a },
+ 'v::m' => sub { my ($x, $y) = map @a[rand @a], 1 .. 2; my $a = $v{$x}; $v{$y} = $a }
+};
--- /dev/null
+#!perl -T
+
+use strict;
+use warnings;
+
+use Test::More tests => 24;
+
+use Variable::Magic qw/wizard getsig cast dispell SIG_MIN/;
+
+my $sig = 300;
+
+my ($a, $b, $c, $d) = 1 .. 4;
+
+{
+ my $wiz = eval { wizard sig => $sig };
+ ok(!$@, "wizard creation error ($@)");
+ ok(defined $wiz, 'wizard is defined');
+ ok(ref $wiz eq 'SCALAR', 'wizard is a scalar ref');
+ ok($sig == getsig $wiz, 'wizard signature is correct');
+
+ my $wiz2 = eval { wizard sig => $sig };
+ ok(!$@, "wizard retrieve error ($@)");
+ ok(defined $wiz2, 'retrieved wizard is defined');
+ ok(ref $wiz2 eq 'SCALAR', 'retrieved wizard is a scalar ref');
+ ok($sig == getsig $wiz2, 'retrieved wizard signature is correct');
+
+ my $a = 1;
+ my $res = eval { cast $a, $wiz };
+ ok(!$@, "cast from wizard croaks ($@)");
+ ok($res, 'cast from wizard invalid');
+
+ $res = eval { dispell $a, $wiz2 };
+ ok(!$@, "dispell from retrieved wizard croaks ($@)");
+ ok($res, 'dispell from retrieved wizard invalid');
+
+ $res = eval { cast $b, $sig };
+ ok(!$@, "cast from integer croaks ($@)");
+ ok($res, 'cast from integer invalid');
+}
+
+my $res = eval { cast $c, $sig + 0.1 };
+ok(!$@, "cast from float croaks ($@)");
+ok($res, 'cast from float invalid');
+
+$res = eval { cast $d, sprintf "%u", $sig };
+ok(!$@, "cast from string croaks ($@)");
+ok($res, 'cast from string invalid');
+
+$res = eval { dispell $b, $sig };
+ok(!$@, "dispell from integer croaks ($@)");
+ok($res, 'dispell from integer invalid');
+
+$res = eval { dispell $c, $sig + 0.1 };
+ok(!$@, "dispell from float croaks ($@)");
+ok($res, 'dispell from float invalid');
+
+$res = eval { dispell $d, sprintf "%u", $sig };
+ok(!$@, "dispell from string croaks ($@)");
+ok($res, 'dispell from string invalid');
+
--- /dev/null
+#!perl -T
+
+use strict;
+use warnings;
+
+use Test::More tests => 19;
+
+use Variable::Magic qw/wizard getdata cast dispell/;
+
+my $c = 1;
+
+my $wiz = eval {
+ wizard data => sub { return { foo => $_[1] || 12, bar => $_[3] || 27 } },
+ get => sub { $c += $_[1]->{foo}; $_[1]->{foo} = $c },
+ set => sub { $c += $_[1]->{bar}; $_[1]->{bar} = $c }
+};
+ok(!$@, "wizard creation error ($@)");
+ok(defined $wiz, 'wizard is defined');
+ok(ref $wiz eq 'SCALAR', 'wizard is a scalar ref');
+
+my $a = 75;
+my $res = eval { cast $a, $wiz };
+ok(!$@, "cast croaks ($@)");
+ok($res, 'cast invalid');
+
+my $data = eval { getdata $a, $wiz };
+ok(!$@, "getdata croaks ($@)");
+ok($res, 'getdata invalid');
+ok($data && ref($data) eq 'HASH'
+ && exists $data->{foo} && $data->{foo} == 12
+ && exists $data->{bar} && $data->{bar} == 27,
+ 'private data creation ok');
+
+my $b = $a;
+ok($c == 13, 'get magic : pass data');
+ok($data->{foo} == 13, 'get magic : data updated');
+
+$a = 57;
+ok($c == 40, 'set magic : pass data');
+ok($data->{bar} == 40, 'set magic : pass data');
+
+$res = eval { dispell $a, $wiz };
+ok(!$@, "dispell croaks ($@)");
+ok($res, 'dispell invalid');
+
+$res = eval { cast $a, $wiz, qw/z j t/ };
+ok(!$@, "cast with arguments croaks ($@)");
+ok($res, 'cast with arguments invalid');
+
+$data = eval { getdata $a, $wiz };
+ok(!$@, "getdata croaks ($@)");
+ok($res, 'getdata invalid');
+ok($data && ref($data) eq 'HASH'
+ && exists $data->{foo} && $data->{foo} eq 'z'
+ && exists $data->{bar} && $data->{bar} eq 't',
+ 'private data creation with arguments ok');
--- /dev/null
+#!perl -T
+
+use strict;
+use warnings;
+
+use Test::More tests => 7;
+
+use Variable::Magic qw/wizard cast/;
+
+my $wiz = eval { wizard get => sub { undef } };
+ok(!$@, "wizard creation error ($@)");
+ok(defined $wiz, 'wizard is defined');
+ok(ref $wiz eq 'SCALAR', 'wizard is a scalar ref');
+
+my $n = int rand 1000;
+my $a = $n;
+
+my $res = eval { cast $a, $wiz };
+ok(!$@, "cast croaks ($@)");
+ok($res, 'cast invalid');
+
+my $x;
+eval {
+ local $SIG{__WARN__} = sub { die };
+ $x = $a
+};
+ok(!$@, 'callback returning undef croaks');
+ok(defined($x) && ($x == $n), 'callback returning undef fails');
--- /dev/null
+#!perl -T
+
+use strict;
+use warnings;
+
+use Test::More tests => 16;
+
+use Variable::Magic qw/wizard cast dispell getdata getsig/;
+
+my $c = 0;
+
+{
+ my $wiz = eval {
+ wizard data => sub { $_[0] },
+ get => sub { ++$c },
+ free => sub { --$c }
+ };
+ ok(!$@, "wizard creation error ($@)");
+ ok(defined $wiz, 'wizard is defined');
+ ok(ref $wiz eq 'SCALAR', 'wizard is a scalar ref');
+
+ my $res = eval { cast $wiz, $wiz };
+ ok(!$@, "cast on self croaks ($@)");
+ ok($res, 'cast on self invalid');
+
+ my $w = $wiz;
+ ok($c == 1, 'magic works correctly on self');
+
+ $res = eval { dispell $wiz, $wiz };
+ ok(!$@, "dispell on self croaks ($@)");
+ ok($res, 'dispell on self invalid');
+
+ $w = $wiz;
+ ok($c == 1, 'magic is no longer invoked on self when dispelled');
+
+ $res = eval { cast $wiz, $wiz, $wiz };
+ ok(!$@, "re-cast on self croaks ($@)");
+ ok($res, 're-cast on self invalid');
+
+ $w = getdata $wiz, $wiz;
+ ok($c == 1, 'getdata on magical self doesn\'t trigger callbacks');
+ # ok(getsig($w) == getsig($wiz), 'getdata returns the correct wizard');
+
+ $res = eval { dispell $wiz, $wiz };
+ ok(!$@, "re-dispell on self croaks ($@)");
+ ok($res, 're-dispell on self invalid');
+
+ $res = eval { cast $wiz, $wiz };
+ ok(!$@, "re-re-cast on self croaks ($@)");
+ ok($res, 're-re-cast on self invalid');
+}
+
+# ok($c == 0, 'magic destructor is called');
--- /dev/null
+#!perl -T
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use Variable::Magic qw/wizard cast dispell VMG_UVAR/;
+
+if (!VMG_UVAR) {
+ plan skip_all => 'No nice uvar magic for this perl';
+}
+
+eval "use Hash::Util::FieldHash qw/fieldhash/";
+if ($@) {
+ plan skip_all => 'Hash::Util::FieldHash required for testing uvar interaction';
+} else {
+ plan tests => 12;
+}
+
+fieldhash(my %h);
+
+bless \(my $obj = {}), 'Variable::Magic::Test::Mock';
+$h{$obj} = 5;
+
+my ($w, $c) = (undef, 0);
+
+eval { $w = wizard fetch => sub { ++$c }, store => sub { --$c } };
+ok(!$@, "wizard with uvar creation error ($@)");
+ok(defined $w, 'wizard with uvar is defined');
+ok(ref($w) eq 'SCALAR', 'wizard with uvar is a scalar ref');
+
+my $res = eval { cast %h, $w };
+ok(!$@, "cast uvar magic on fieldhash croaks ($@)");
+ok($res, 'cast uvar magic on fieldhash invalid');
+
+my $s = $h{$obj};
+ok($s == 5, 'fetch magic on fieldhash doesn\'t clobber');
+ok($c == 1, 'fetch magic on fieldhash');
+
+$h{$obj} = 7;
+ok($c == 0, 'store magic on fieldhash');
+ok($h{$obj} == 7, 'store magic on fieldhash doesn\'t clobber'); # $c == 1
+
+$res = eval { dispell %h, $w };
+ok(!$@, "dispell uvar magic on fieldhash croaks ($@)");
+ok($res, 'dispell uvar magic on fieldhash invalid');
+
+$h{$obj} = 11;
+$s = $h{$obj};
+ok($s == 11, 'store/fetch on fieldhash after dispell still ok');
}
SKIP: {
- eval "use Tie::Has";
+ eval "use Tie::Hash";
skip 'Tie::Hash required to test copy magic on hashes', 14 if $@;
tie my %h, 'Tie::StdHash';
use Variable::Magic qw/wizard cast dispell VMG_UVAR/;
if (VMG_UVAR) {
- plan tests => 16;
+ plan tests => 20;
} else {
plan skip_all => 'No nice uvar magic for this perl';
}
my %h = (a => 1, b => 2, c => 3);
my $res = cast %h, $wiz;
-
ok($res, 'uvar : cast succeeded');
ok(check(), 'uvar : cast didn\'t triggered the callback');
ok(check(), 'uvar : delete non-existing key');
ok(!defined $x, 'uvar : delete non-existing key correctly');
+my $wiz2 = wizard 'fetch' => sub { 0 };
+my %h2 = (a => 37, b => 2, c => 3);
+cast %h2, $wiz2;
+
+eval {
+ local $SIG{__WARN__} = sub { die };
+ $x = $h2{a};
+};
+ok(!$@, 'uvar : fetch with incomplete magic');
+ok($x == 37, 'uvar : fetch with incomplete magic correctly');
+
+eval {
+ local $SIG{__WARN__} = sub { die };
+ $h2{a} = 73;
+};
+ok(!$@, 'uvar : store with incomplete magic');
+ok($h2{a} == 73, 'uvar : store with incomplete magic correctly');
use Test::More tests => 21;
-use Variable::Magic qw/wizard cast dispell/;
+use Variable::Magic qw/wizard cast dispell VMG_COMPAT_ARRAY_PUSH_NOLEN VMG_COMPAT_ARRAY_UNDEF_CLEAR/;
my @c = (0) x 12;
my @x = (0) x 12;
ok(check(), 'array : assign new element');
push @a, 'x';
-++$x[1]; ++$x[2] unless $^V && $^V gt 5.9.2; # since 5.9.3
+++$x[1]; ++$x[2] unless VMG_COMPAT_ARRAY_PUSH_NOLEN;
ok(check(), 'array : push');
pop @a;
ok(check(), 'array : scope end');
undef @a;
-++$x[3] if $^V && $^V gt 5.9.4; # since 5.9.5 - see #43357
+++$x[3] if VMG_COMPAT_ARRAY_UNDEF_CLEAR;
ok(check(), 'array : undef');
dispell @a, $wiz;
use strict;
use warnings;
-use Test::More tests => 17;
+use Test::More tests => 18;
-use Variable::Magic qw/wizard cast dispell MGf_COPY VMG_UVAR/;
+use Variable::Magic qw/wizard cast dispell MGf_COPY VMG_UVAR VMG_COMPAT_HASH_LISTASSIGN_COPY/;
my @c = (0) x 12;
my @x = (0) x 12;
$x[8] += 2 if VMG_UVAR;
ok(check(), 'hash : slice');
+%a = (a => 1, d => 3);
+++$x[3];
+$x[5] += 2 if VMG_COMPAT_HASH_LISTASSIGN_COPY;
+$x[9] += 2 if VMG_UVAR;
+ok(check(), 'hash : assign from list');
+
%a = map { $_ => 1 } qw/a b d/;
++$x[3];
-$x[5] += 3 if MGf_COPY && $^V && $^V gt 5.9.3;
+$x[5] += 3 if VMG_COMPAT_HASH_LISTASSIGN_COPY;
$x[9] += 3 if VMG_UVAR;
-ok(check(), 'hash : assign');
+ok(check(), 'hash : assign from map');
$a{d} = 2;
++$x[5] if MGf_COPY;