Skip Menu |

This queue is for tickets about the Class-Data-Inheritable-Translucent CPAN distribution.

Report information
The Basics
Id: 51097
Status: resolved
Priority: 0/
Queue: Class-Data-Inheritable-Translucent

People
Owner: Nobody in particular
Requestors: shay [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: Critical
Broken in: 0.01
Fixed in: (no value)



Subject: [PATCH] Setting data on a sub-class object undefs the sub-class translucent default value
Setting data on a sub-class object undefs the sub-class's translucent default value. The following program demonstrates the problem: - test.pl - use Class::Data::Inheritable::Translucent; package MyClass; use base qw(Class::Data::Inheritable::Translucent); MyClass->mk_translucent(attr => 'foo'); sub new { return bless {}, shift } package MySubClass; use base qw(MyClass); package main; my $default = MySubClass->attr(); printf "Default='%s'\n", defined $default ? $default : '<undef>'; my $obj = MySubClass->new(); # Overriding the default on a sub-class object clobbers its default! $obj->attr('bar'); $default = MySubClass->attr(); printf "Default now='%s'\n", defined $default ? $default : '<undef>'; __END__ That program prints out: Default='foo' Default now='<undef>' but it ought to print out: Default='foo' Default now='foo' The output is as expected if the line $obj->attr('bar'); is deleted, i.e. it is the setting of the data on the sub-class object that causes this ill effect. The problem is the following two lines in the module: return $wantclass->mk_translucent($attribute)->(@_) if @_>1 && $wantclass ne $declaredclass; That idiom is copied from Class::Data::Inheritable, where it works fine, but it doesn't work here. The trouble is that the mk_translucent() call in there installs an override for $attribute into $wantclass but doesn't pass in the third argument--the translucent default value! The default value is therefore taken to be 'undef'. The ->(@_) bit on the end then sets the value correctly on the sub-class object, the damage has already been to the sub-class default value. (In Class::Data::Inheritable, they get away with it because they are only dealing with class data. An override is installed for the sub-class with an initial value of 'undef' as here, and then the ->(@_) bit immediately sets that initial 'undef' value to what it ought to be *for the sub-class*--not just for an object, because the method doesn't operate on object attributes in that case.) The attached patch fixes the bug by ensuring that the parent class's current default value gets set as the sub-class's default value when installing an override into the sub-class. Of course, it still leaves a different undesirable behaviour, which could be seen as another bug, in that simply installing an override for a sub-class *object* attribute value causes that sub-class's default value to be overridden itself. You might have expected the sub-class to continue *inheriting* the parent class's original default value, rather than now using a *copy* of its then-current value. (It should only acquire its own separate copy of the default value if the attribute is overridden on the sub-class itself, rather than on an instance of the sub-class.) An example might be clearer: You might have expected this: - test2.pl - use Class::Data::Inheritable::Translucent; package MyClass; use base qw(Class::Data::Inheritable::Translucent); MyClass->mk_translucent(attr => 'foo'); sub new { return bless {}, shift } package MySubClass; use base qw(MyClass); package main; my $obj = MySubClass->new(); # Overriding the default on an object makes the sub-class copy # the current parent class default! $obj->attr('bar'); MyClass->attr('baz'); $default = MyClass->attr(); printf "Class default='%s'\n", defined $default ? $default : '<undef>'; $default = MySubClass->attr(); printf "Sub-class default='%s'\n", defined $default ? $default : '<undef>'; __END__ to print this: Class default='baz' Sub-class default='baz' rather than this: Class default='baz' Sub-class default='foo' (Without the attached patch, of course, it prints: Class default='baz' Sub-class default='<undef>' which is even worse.) Unfortunately, I don't currently have a solution to the second problem, but I think the first problem would at least be worth fixing.
Subject: override.patch
diff -ruN Class-Data-Inheritable-Translucent-0.01/lib/Class/Data/Inheritable/Translucent.pm Class-Data-Inheritable-Translucent-0.01.orig/lib/Class/Data/Inheritable/Translucent.pm --- Class-Data-Inheritable-Translucent-0.01/lib/Class/Data/Inheritable/Translucent.pm 2009-11-04 08:35:31.042194900 +0000 +++ Class-Data-Inheritable-Translucent-0.01.orig/lib/Class/Data/Inheritable/Translucent.pm 2005-12-15 20:53:06.000000000 +0000 @@ -60,7 +60,7 @@ my $obj = ref($_[0]) ? $_[0] : undef; my $wantclass = ref($_[0]) || $_[0]; - return $wantclass->mk_translucent($attribute, $data)->(@_) + return $wantclass->mk_translucent($attribute)->(@_) if @_>1 && $wantclass ne $declaredclass; if ($obj) {
On Wed Nov 04 03:48:33 2009, SHAY wrote: Show quoted text
> Of course, it still leaves a different undesirable behaviour, which > could be seen as another bug, in that simply installing an override for > a sub-class *object* attribute value causes that sub-class's default > value to be overridden itself. You might have expected the sub-class to > continue *inheriting* the parent class's original default value, rather > than now using a *copy* of its then-current value. (It should only > acquire its own separate copy of the default value if the attribute is > overridden on the sub-class itself, rather than on an instance of the > sub-class.) An example might be clearer: You might have expected this: > > - test2.pl - > use Class::Data::Inheritable::Translucent; > > package MyClass; > use base qw(Class::Data::Inheritable::Translucent); > MyClass->mk_translucent(attr => 'foo'); > sub new { return bless {}, shift } > > package MySubClass; > use base qw(MyClass); > > package main; > > my $obj = MySubClass->new(); > # Overriding the default on an object makes the sub-class copy > # the current parent class default! > $obj->attr('bar'); > > MyClass->attr('baz'); > > $default = MyClass->attr(); > printf "Class default='%s'\n", defined $default ? $default : '<undef>'; > > $default = MySubClass->attr(); > printf "Sub-class default='%s'\n", defined $default ? $default :
'<undef>'; Show quoted text
> __END__ > > to print this: > > Class default='baz' > Sub-class default='baz' > > rather than this: > > Class default='baz' > Sub-class default='foo' > > (Without the attached patch, of course, it prints: > > Class default='baz' > Sub-class default='<undef>' > > which is even worse.) > > Unfortunately, I don't currently have a solution to the second problem, > but I think the first problem would at least be worth fixing.
Actually, a solution to that second problem just sprang to mind. It is simply this: An override should not be installed for the sub-class unless the attribute was actually set on the sub-class (as opposed to an instance of the sub-class), which is easily arranged by checking $obj is false before installing the override. The attached patch does exactly this and fixes both bugs :-)
diff -ruN Class-Data-Inheritable-Translucent-0.01/lib/Class/Data/Inheritable/Translucent.pm Class-Data-Inheritable-Translucent-0.01.orig/lib/Class/Data/Inheritable/Translucent.pm --- Class-Data-Inheritable-Translucent-0.01/lib/Class/Data/Inheritable/Translucent.pm 2009-11-04 09:07:09.198444900 +0000 +++ Class-Data-Inheritable-Translucent-0.01.orig/lib/Class/Data/Inheritable/Translucent.pm 2005-12-15 20:53:06.000000000 +0000 @@ -60,8 +60,8 @@ my $obj = ref($_[0]) ? $_[0] : undef; my $wantclass = ref($_[0]) || $_[0]; - return $wantclass->mk_translucent($attribute, $data)->(@_) - if @_>1 && !$obj && $wantclass ne $declaredclass; + return $wantclass->mk_translucent($attribute)->(@_) + if @_>1 && $wantclass ne $declaredclass; if ($obj) { my $attrs = $obj->attrs;
On Wed Nov 04 04:14:39 2009, SHAY wrote: Show quoted text
> The attached patch does exactly this and fixes both bugs :-)
Apologies, that patch was reversed. Here's the same patch the correct way round.
diff -ruN Class-Data-Inheritable-Translucent-0.01.orig/lib/Class/Data/Inheritable/Translucent.pm Class-Data-Inheritable-Translucent-0.01/lib/Class/Data/Inheritable/Translucent.pm --- Class-Data-Inheritable-Translucent-0.01.orig/lib/Class/Data/Inheritable/Translucent.pm 2005-12-15 20:53:06.000000000 +0000 +++ Class-Data-Inheritable-Translucent-0.01/lib/Class/Data/Inheritable/Translucent.pm 2009-11-04 09:25:50.479694900 +0000 @@ -60,8 +60,8 @@ my $obj = ref($_[0]) ? $_[0] : undef; my $wantclass = ref($_[0]) || $_[0]; - return $wantclass->mk_translucent($attribute)->(@_) - if @_>1 && $wantclass ne $declaredclass; + return $wantclass->mk_translucent($attribute, $data)->(@_) + if @_>1 && !$obj && $wantclass ne $declaredclass; if ($obj) { my $attrs = $obj->attrs;
Of course, the part of override3.patch that fixes the second problem actually also fixes the first problem anyway ;-) That fix is now applied in version 1.00, which I've just released myself after the original module author passed ownership of the module to me.