Skip Menu |
Report information
Id: 130246
Status: open
Priority: 0/
Queue: perl6

Owner: Nobody
Requestors: TimTom <tim.bollman [at] live.com>
Cc:
AdminCc:

Severity: (no value)
Tag: Bug
Platform: (no value)
Patch Status: (no value)
VM: (no value)



Subject: [BUG] Attribute type validation with slipped constructor
To: "rakudobug [...] perl.org" <rakudobug [...] perl.org>
Date: Fri, 2 Dec 2016 21:13:06 +0000
From: Tim Bollman <Tim.Bollman [...] live.com>
Download (untitled) / with headers
text/plain 797b

If you create a typed array attribute for a class and then attempt to assign it in the constructor using a hash slip, it fails type validation.


Error message: Type check failed in assignment to @!b; expected Str but got Array ($["a", "b", "c"])


use v6;
use Test;

plan 8;
class A {
    has @.b;
}
class A-validate {
    has Str @b;
}
my $a;
my @b = <a b c>;
my %attr = (b => @b);
lives-ok { $a = A.new(b => @b) }, 'A Created';
is($a.b, @b, 'A.b valid');
lives-ok { $a = A.new(|%attr) }, 'A created (slip)';
is($a.b, @b, 'A.b valid (slip)');
lives-ok { $a = A-validate.new(b => @b) }, 'A Created (validate)';
is($a.b, @b, 'A.b valid (validate)');
lives-ok { $a = A-validate.new(|%attr) }, 'A created (validate, slip)'; # fails
is($a.b, @b, 'A.b valid (validate, slip)');


Thanks,

Tim Bollman

RT-Send-CC: perl6-compiler [...] perl.org
Download (untitled) / with headers
text/plain 1.4k
On Fri, 02 Dec 2016 13:13:36 -0800, TimTom wrote: Show quoted text
> If you create a typed array attribute for a class and then attempt to > assign it in the constructor using a hash slip, it fails type > validation. > > > Error message: Type check failed in assignment to @!b; expected Str > but got Array ($["a", "b", "c"]) > > > use v6; > use Test; > > plan 8; > class A { > has @.b; > } > class A-validate { > has Str @b; > } > my $a; > my @b = <a b c>; > my %attr = (b => @b); > lives-ok { $a = A.new(b => @b) }, 'A Created'; > is($a.b, @b, 'A.b valid'); > lives-ok { $a = A.new(|%attr) }, 'A created (slip)'; > is($a.b, @b, 'A.b valid (slip)'); > lives-ok { $a = A-validate.new(b => @b) }, 'A Created (validate)'; > is($a.b, @b, 'A.b valid (validate)'); > lives-ok { $a = A-validate.new(|%attr) }, 'A created (validate, > slip)'; # fails > is($a.b, @b, 'A.b valid (validate, slip)'); > > > Thanks, > > Tim Bollman
This is the crux of the issue: dd [ | (b => []) ]; # [:b([])] dd [ |%(b => []) ]; # [:b($[])] A pair (slipped or not), does not itemize the array, so the array's values get assigned to the attribute, but if such a pair has lived in a hash, the array gets itemized, and instead of its values being, passed into the attribute, the array itself--as a single item--gets passed instead, which in the second case presented above, triggers the type mismatch. I don't know whether that's a bug or whether there's anything we can do about this behaviour.
Download (untitled) / with headers
text/plain 2.8k
On Fri, 02 Dec 2016 15:59:27 -0800, cpan@zoffix.com wrote: Show quoted text
> On Fri, 02 Dec 2016 13:13:36 -0800, TimTom wrote:
> > If you create a typed array attribute for a class and then attempt to > > assign it in the constructor using a hash slip, it fails type > > validation. > > > > > > Error message: Type check failed in assignment to @!b; expected Str > > but got Array ($["a", "b", "c"]) > > > > > > use v6; > > use Test; > > > > plan 8; > > class A { > > has @.b; > > } > > class A-validate { > > has Str @b; > > } > > my $a; > > my @b = <a b c>; > > my %attr = (b => @b); > > lives-ok { $a = A.new(b => @b) }, 'A Created'; > > is($a.b, @b, 'A.b valid'); > > lives-ok { $a = A.new(|%attr) }, 'A created (slip)'; > > is($a.b, @b, 'A.b valid (slip)'); > > lives-ok { $a = A-validate.new(b => @b) }, 'A Created (validate)'; > > is($a.b, @b, 'A.b valid (validate)'); > > lives-ok { $a = A-validate.new(|%attr) }, 'A created (validate, > > slip)'; # fails > > is($a.b, @b, 'A.b valid (validate, slip)'); > > > > > > Thanks, > > > > Tim Bollman
> > > This is the crux of the issue: > > dd [ | (b => []) ]; # [:b([])] > dd [ |%(b => []) ]; # [:b($[])] > > A pair (slipped or not), does not itemize the array, so the array's > values get assigned to the attribute, > but if such a pair has lived in a hash, the array gets itemized, and > instead of its values being, > passed into the attribute, the array itself--as a single item--gets > passed instead, which in the > second case presented above, triggers the type mismatch. > > I don't know whether that's a bug or whether there's anything we can > do about this behaviour.
Ah, I had thought the is($a.b, @b) would validate that the slipped version was assigning as a set of 3 elements. But if you add a check directly on .elems it returns 1. So at least the assignment is consistently using it as an array of one item, while I thought it was type-checking it one way and assigning as I would expect. I figured that |%args was the way to go because it's the natural way to adjust arguments in a factory constructor like the following. Where you check the arguments for which variant you want to build, adjust parameters accordingly (for example turning string name references into real references), then call the real object constructor. In particular, it prevents you from doing something like: sub build(*%args is copy) { %args<b> = %args<b>.map( { perform-magic $_ }); A.new(|%args) } Without the is copy, that passes type validation, except if you pass in something like b => @b, the assignment will modify the actual elements of @b which is not ideal in practice. You could instead do something like my @new-b = ...; A.new(|%args, \(b => @new-b)); which works as long you can quantify all the parameters you are adjusting, but as things get more complicated, that's not always possible. Expanded test case attached.
Subject: validate-error.p6
use v6; use Test; class A { has @.b; } class A-validate { has Str @.b; } my @b = <a b c>; my %attr = (b => @b); my $attr = \(b => @b); my %construct = ( 'inline' => { .new(b => @b) }, 'hash slip' => { .new(|%attr) }, 'capture' => { .new(|$attr) }, 'inner params' => sub ($type) { sub do-it(*%args) { %args<b> = %args<b>.map( { $_ }); $type.new(|%args); } do-it(b => @b); }, 'inner params copy' => sub ($type) { sub do-it(*%args is copy) { %args<b> = %args<b>.map( { $_ } ); $type.new(|%args); } do-it(b => @b); } ); plan +(A, A-validate) * %construct.elems * 3; for (A, A-validate) -> $type { for %construct.kv -> $version, $construct { my $a; lives-ok { $a = $construct.($type) }, "Instance Created ({$type.^name} $version)"; if ($a.defined) { is($a.b, @b, "A.b valid ({$type.^name} $version)"); is(+$a.b, 3, "A.b length ({$type.^name} $version)"); } } }


This service is sponsored and maintained by Best Practical Solutions and runs on Perl.org infrastructure.

For issues related to this RT instance (aka "perlbug"), please contact perlbug-admin at perl.org