Composite generators in bind cause type error when shrinking
Closed this issue · 3 comments
This is the simplest example I could come up with that reproduces the error:
$this->forAll(
Generator\bind(
Generator\choose(1, 10),
function ($x) {
return Generator\tuple($x);
}
)
)->then(function ($tuple) {
$this->assertTrue(false, var_export($tuple, true));
});
The error is:
Error: Argument 1 passed to Eris\Generator\TupleGenerator::shrink()
must be an instance of Eris\Generator\GeneratedValueSingle,
instance of Eris\Generator\GeneratedValueOptions given,
called in /home/me/project/vendor/giorgiosironi/eris/src/Generator/BindGenerator.php on line 42
It seems to happen with composite generators being returned from inside of a bind
generator function.
I've tried with tuple and associative.
In practice a GeneratedValueOptions
should work in shrink
, too, since the GeneratedValueOptions
instance also has a input
method, but I'm not sure how the type signature should be fixed.
The \Eris\Generator\TupleGenerator::shrink(GeneratedValueSingle $element)
signature can't be changed because it has to match \Eris\Generator::shrink(GeneratedValueSingle $element)
.
Maybe if the argument type where changed to \Eris\Generator\GeneratedValue
instead, and that interface then implemented the input
method?
That would be a backward compatibility breaking change, but at the moment I'm not sure how to build generators that are related to other generated values without using composite generators with bind
.
As a workaround (a.k.a. hacky patch), I've added the following to the \Eris\Generator\BindGenerator::shrink
method:
while ($outerGeneratorValue instanceof GeneratedValueOptions) {
$outerGeneratorValue = $outerGeneratorValue->last();
}
Now the bind generator work as expected.
This is the complete shrink
method:
public function shrink(GeneratedValueSingle $element)
{
list($outerGeneratorValue, $innerGeneratorValue) = $element->input();
// TODO: shrink also the second generator
$outerGenerator = call_user_func($this->outerGeneratorFactory, $innerGeneratorValue->unbox());
while ($outerGeneratorValue instanceof GeneratedValueOptions) {
$outerGeneratorValue = $outerGeneratorValue->last();
}
$shrinkedOuterGeneratorValue = $outerGenerator->shrink($outerGeneratorValue);
return $this->packageGeneratedValueSingle(
$shrinkedOuterGeneratorValue,
$innerGeneratorValue
);
}
I have no idea if I broke something else by adding this line... It doesn't seem like the right fix. Just thought I'd add the info here because it might be helpful.
I've narrowed it down a bit. The error happens on the second shrink. It can be reproduced with the following test (in \Eris\Generator\BindGeneratorTest
):
public function testShrinkBindGeneratorWithCompositeValue()
{
$bindGenerator = new BindGenerator(
new ChooseGenerator(0, 5),
function ($n) {
return new TupleGenerator([$n]);
}
);
$generatedValue = $bindGenerator->__invoke(10, new RandomRange(new RandSource()));
$firstShrunkValue = $bindGenerator->shrink($generatedValue);
$secondShrunkValue = $bindGenerator->shrink($firstShrunkValue);
$this->assertInstanceOf('\Eris\Generator\GeneratedValue', $secondShrunkValue);
}
From what I can see the problem is that according to the return type hint \Eris\Generator::shrink()
returns either a GeneratedValueSingle<T>
or a GeneratedValueOptions<T>
, but the method argument allows onlyGeneratedValueSingle
instances.
This breaks shrinking for generators that indeed return GeneratedValueOptions
.
I assume that #127 fixes this. Feel free to reopen otherwise.