Skip Menu |

This queue is for tickets about the Type-Tiny CPAN distribution.

Report information
The Basics
Id: 97840
Status: rejected
Priority: 0/
Queue: Type-Tiny

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

Bug Information
Severity: (no value)
Broken in: 0.047_08
Fixed in: (no value)



Subject: incorrect argument fingered in validate w/ optional coerced arg and bogus extra arg
The attached code illustrates this problem: cfg_path is an optional class_type argument which may be coerced into a class from a string. If validate() is passed a valid cfg_path plus a bogus extra parameter, cfg_path's validation fails; the coercion is not performed, but the class check is still performed. The correct error would be that the extra parameter is bogus. Here's the output on a clean install of Perl 5.20.0 with Type::Tiny 0.047_08 (fails on 0.046 as well): e% perl bug2.t ok 1 - just the optional argument ok 2 - 'the coerced optional argument' isa 'Foo' ok 3 - an unexpected argument not ok 4 - both the optional and unexpected arguments # Failed test 'both the optional and unexpected arguments' # at bug2.t line 51. # 'Reference {"cfg_path" => ".","shell" => "."} did not pass type constraint "Dict[cfg_path=>Optional[Path]]" (in $_[0]) at /home/dj/.perlbrew/libs/perl-5.20.0@tmp4/lib/perl5/Test/Fatal.pm line 45 # Reference {"cfg_path" => ".","shell" => "."} did not pass type constraint "Dict[cfg_path=>Optional[Path]]" (in $_[0]) # "Dict[cfg_path=>Optional[Path]]" constrains value at key "cfg_path" of hash with "Optional[Path]" # Value "." did not pass type constraint "Optional[Path]" (in $_[0]->{"cfg_path"}) # $_[0]->{"cfg_path"} exists # "Optional[Path]" constrains $_[0]->{"cfg_path"} with "Path" if it exists # Not a blessed reference # ' # doesn't match '(?^:does not allow key "shell")' 1..4 # Looks like you failed 1 test of 4.
Subject: bug2.t
#! perl use strict; use warnings; use Test::More; use Test::Fatal; { package Foo; sub new { bless {}, shift } }; use Types::Standard qw[ Str Dict Optional ]; use Type::Params qw[ validate ]; use Type::Utils -all; use Type::Library -base, -declare => qw( Path ); class_type Path, { class => "Foo" }; coerce Path, from Str() => q{ Foo->new($_) }, ; my @spec = ( Dict [ cfg_path => Optional [Path] ] ); my %args = @ARGV; is( exception { validate( [ {cfg_path => '.'} ], @spec ); }, undef, 'just the optional argument', ); isa_ok( (validate( [ {cfg_path => '.'} ], @spec ))[0]->{cfg_path}, 'Foo', 'the coerced optional argument'); like( exception { validate( [ { shell => '.'} ], @spec ); }, qr/does not allow key "shell"/, 'an unexpected argument', ); like( exception { validate( [ { cfg_path => '.', shell => '.'} ], @spec ); }, qr/does not allow key "shell"/, 'both the optional and unexpected arguments', ); done_testing;
On Thu Aug 07 14:25:02 2014, DJERIUS wrote: Show quoted text
> > Here's the output on a clean install of Perl 5.20.0 with Type::Tiny > 0.047_08 (fails on 0.046 as well): >
I was incorrect; this does not fail on 0.046, only on 0.047_08. I've isolated another means of triggering the error message, this time without requiring an unexpected parameter. Here it requires two parameters requiring coercion: ok 1 - missing required parameter not ok 2 - optional arg, missing required # Failed test 'optional arg, missing required' # at bug3.t line 49. # 'Reference {"cfg_path" => "."} did not pass type constraint "Dict[cfg_path=>Optional[Path],shell=>Str,tilt_angle=>Optional[Angle]]" (in $_[0]) at /home/dj/.perlbrew/libs/perl-5.20.0@tmp4/lib/perl5/Test/Fatal.pm line 45 # Reference {"cfg_path" => "."} did not pass type constraint "Dict[cfg_path=>Optional[Path],shell=>Str,tilt_angle=>Optional[Angle]]" (in $_[0]) # "Dict[cfg_path=>Optional[Path],shell=>Str,tilt_angle=>Optional[Angle]]" constrains value at key "cfg_path" of hash with "Optional[Path]" # Value "." did not pass type constraint "Optional[Path]" (in $_[0]->{"cfg_path"}) # $_[0]->{"cfg_path"} exists # "Optional[Path]" constrains $_[0]->{"cfg_path"} with "Path" if it exists # Not a blessed reference # ' # doesn't match '(?^:requires key "shell")' ok 3 - optional arg, missing required, removed other optional arg from spec 1..3 # Looks like you failed 1 test of 3.
Subject: bug3.t
#! perl use strict; use warnings; use Test::More; use Test::Fatal; use Types::Standard -all; use Types::Common::Numeric -all; use Type::Params qw[ validate ]; use Type::Utils -all; { package Foo; sub new { bless {}, shift } }; use Type::Library -base, -declare => qw( Path Angle ); class_type Path, { class => "Foo" }; coerce Path, from Str , q{ Foo->new($_) }, ; declare Angle, as Num; coerce Angle, from Str, via { }; my %ARG = ( cfg_path => '.' ); like ( exception { validate( [ { } ], Dict [ cfg_path => Optional [Path], shell => Str, tilt_angle => Optional [Angle], ] ); }, qr/requires key "shell"/, 'missing required parameter' ); like ( exception { validate( [ { cfg_path => '.' } ], Dict [ cfg_path => Optional [Path], shell => Str, tilt_angle => Optional [Angle], ] ); }, qr/requires key "shell"/, 'optional arg, missing required' ); like ( exception { validate( [ { cfg_path => '.' } ], Dict [ cfg_path => Optional [Path], shell => Str, ] ); }, qr/requires key "shell"/, 'optional arg, missing required, removed other optional arg from spec' ); done_testing;
On Thu Aug 07 15:04:57 2014, DJERIUS wrote: Show quoted text
> On Thu Aug 07 14:25:02 2014, DJERIUS wrote: >
> > > > Here's the output on a clean install of Perl 5.20.0 with Type::Tiny > > 0.047_08 (fails on 0.046 as well): > >
> > I was incorrect; this does not fail on 0.046, only on 0.047_08.
Today is my day to be incorrect (or have too many terminals open). The indicated failures in the tests in file "bug2.t" fail on both 0.046 and 0.047_08. The indicated failures in "bug3.t" only occur on 0.047_08.
I don't consider this to be a bug. The type coercion and type check are two separate steps. Coercion either happens 100% or it does not happen at all. In this case, the data structure you're passing in cannot be coerced (because the automatic deep coercion for Dict stops short of deleting keys from the hashref). Coercion has failed. Now we move on to the type check. Because there was nothing output from the coercion, the type check happens on the *original hashref* which was: { cfg_path => '.', shell => '.' } This is checked against: Dict[ cfg_path => Optional[Path] ] It fails, and you get the reason: Value "." did not pass type constraint "Optional[Path]" Which is true. I grant you that it's not entirely intuitive, but it does make sense when you consider coercion and check to be two separate stages, and that the check happens on the original input if the coercion has failed.
On Fri Aug 08 06:21:53 2014, TOBYINK wrote: Show quoted text
> I don't consider this to be a bug. > > The type coercion and type check are two separate steps. > > Coercion either happens 100% or it does not happen at all. In this > case, the data structure you're passing in cannot be coerced (because > the automatic deep coercion for Dict stops short of deleting keys from > the hashref).
Does this mean that the check for extra or missing elements happens twice, once during type coercion of Dict (which apparently fails silently), and then during type checking, but apparently only after checking types for passed elements? A container is special, especially one like Dict, which limits membership based upon name as well as type. Why not check that the set of passed parameters is complete before doing any other work? Show quoted text
> > Coercion has failed. Now we move on to the type check. Because there > was nothing output from the coercion, the type check happens on the > *original hashref* which was: > > { cfg_path => '.', shell => '.' } > > This is checked against: > > Dict[ cfg_path => Optional[Path] ] > > It fails, and you get the reason: > > Value "." did not pass type constraint "Optional[Path]" > > Which is true. > > I grant you that it's not entirely intuitive, but it does make sense > when you consider coercion and check to be two separate stages, and > that the check happens on the original input if the coercion has > failed.
In practice, this means that one should never use a coercion in a Dict, as there's no way of knowning which element caused the validation process to fail. That has significant repercussions. You can't use coerced types in slurpy Dict's used to provide named parameters to functions in the public API. User's have to be able to figure out which parameter caused the problem. Unfortunately, this is exactly where you'd want to be able to provide the flexibility that coercions give. Type libraries are now suspect. Prior to being pared down for the bug report, the code used Types::Path::Tiny to ensure that a passed path was valid. Now each library type has to be checked that it doesn't have an attached coercion. This significantly limits what's possible with a Dict. That's unfortunate. Dict's (sole) purpose is to guarantee a fixed set of named parameters, but it cannot guarantee that if that requirement is violated that it can provide correct information as to the problem. That seems like a bug. Diab
On Fri Aug 08 10:59:29 2014, DJERIUS wrote: [... many things ...] Rather than continue to guess as to what's happening in the code, I'm going to have to take a dive into that section. If there is some non-grotty fashion of ensuring that violations of container constraints are reported as such, would you accept code to do so, or is there a more fundamental philosophical reason for not pursuing it? Thanks, Diab
On 2014-08-08T15:59:29+01:00, DJERIUS wrote: Show quoted text
> Does this mean that the check for extra or missing elements happens > twice, once during type coercion of Dict (which apparently fails > silently), and then during type checking, but apparently only after > checking types for passed elements?
Coercions will always result in two checks. The first is to check if the value is something that needs to be coerced; the second check is necessary to ensure that the output of the coercion does indeed pass the type constraint. (Coercions after all accept arbitrary coderefs, which may accidentally return a value that does not pass the type constraint.) Show quoted text
> In practice, this means that one should never use a coercion in a > Dict, as there's no way of knowning which element caused the > validation process to fail. That has significant repercussions.
The biggest thing you usually want to know is *whether* it failed. Show quoted text
> You can't use coerced types in slurpy Dict's used to provide named > parameters to functions in the public API. User's have to be able > to figure out which parameter caused the problem. Unfortunately, this > is exactly where you'd want to be able to provide the flexibility that > coercions give.
The use of slurpy dicts with Type::Params is mostly a workaround for the fact that the compile/validate functions are designed for positional parameters. So it's really just considering your named parameters as a single big hashref parameter. When validation fails, it tells you exactly which parameter was the problem - the single big hashref one! :-) Ultimately this would be improved by Type::Params providing compile_named/validate_named counterparts, so that each named parameter could be coerced and validated separately. Mithaldu has also requested this. It's likely to end up as a 1.002000 feature.