Skip to content

Commit 8d7bb37

Browse files
committed
fix getValues() invalid type
1 parent ad594d4 commit 8d7bb37

File tree

3 files changed

+89
-95
lines changed

3 files changed

+89
-95
lines changed

src/Type/Nette/FormContainerValuesDynamicReturnTypeExtension.php

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,52 @@ public function isMethodSupported(MethodReflection $methodReflection): bool
2626
return $methodReflection->getName() === 'getValues';
2727
}
2828

29-
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
29+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
3030
{
31-
if (count($methodCall->getArgs()) === 0) {
31+
$args = $methodCall->getArgs();
32+
33+
// No argument => default object
34+
if (count($args) === 0) {
3235
return new ObjectType('Nette\Utils\ArrayHash');
3336
}
3437

35-
$arg = $methodCall->getArgs()[0]->value;
38+
$arg = $args[0]->value;
3639
$scopedType = $scope->getType($arg);
37-
if ($scopedType->isTrue()->yes()) {
40+
41+
if ($scopedType->isBoolean()->yes()) {
42+
if ($scopedType->isTrue()->yes()) {
43+
return new ArrayType(new StringType(), new MixedType());
44+
}
45+
46+
// boolean false → object
47+
if ($scopedType->isFalse()->yes()) {
48+
return new ObjectType('Nette\Utils\ArrayHash');
49+
}
50+
}
51+
52+
$constantStrings = $scopedType->getConstantStrings();
53+
54+
if (count($constantStrings) === 0) {
55+
return new ObjectType('Nette\Utils\ArrayHash');
56+
}
57+
58+
$constantString = $constantStrings[0];
59+
60+
$value = $constantString->getValue();
61+
62+
if ($scopedType->isClassString()->yes()) {
63+
return $scopedType->getClassStringObjectType();
64+
}
65+
66+
if ($value === 'array') {
3867
return new ArrayType(new StringType(), new MixedType());
3968
}
40-
if ($scopedType->isFalse()->yes()) {
69+
70+
if ($value === 'object') {
4171
return new ObjectType('Nette\Utils\ArrayHash');
4272
}
4373

44-
return null;
74+
return new ObjectType('Nette\Utils\ArrayHash');
4575
}
4676

4777
}

tests/Type/Nette/FormContainerValuesDynamicReturnTypeExtensionTest.php

Lines changed: 19 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,106 +2,36 @@
22

33
namespace PHPStan\Type\Nette;
44

5-
use Nette\Utils\ArrayHash;
6-
use PhpParser\Node\Arg;
7-
use PhpParser\Node\Expr;
8-
use PhpParser\Node\Expr\MethodCall;
9-
use PHPStan\Analyser\Scope;
10-
use PHPStan\Reflection\FunctionVariant;
11-
use PHPStan\Reflection\MethodReflection;
12-
use PHPStan\Type\ArrayType;
13-
use PHPStan\Type\Constant\ConstantBooleanType;
14-
use PHPStan\Type\Generic\TemplateTypeMap;
15-
use PHPStan\Type\IterableType;
16-
use PHPStan\Type\MixedType;
17-
use PHPStan\Type\ObjectType;
18-
use PHPStan\Type\UnionType;
19-
use PHPStan\Type\VerbosityLevel;
20-
use PHPUnit\Framework\TestCase;
5+
use PHPStan\Testing\TypeInferenceTestCase;
216

22-
final class FormContainerValuesDynamicReturnTypeExtensionTest extends TestCase
7+
final class FormContainerValuesDynamicReturnTypeExtensionTest extends TypeInferenceTestCase
238
{
249

25-
private FormContainerValuesDynamicReturnTypeExtension $extension;
26-
27-
protected function setUp(): void
10+
/**
11+
* @return iterable<string, mixed[]>
12+
*/
13+
public static function dataFileAsserts(): iterable
2814
{
29-
$this->extension = new FormContainerValuesDynamicReturnTypeExtension();
15+
yield from self::gatherAssertTypes(__DIR__ . '/data/FormContainerModel.php');
3016
}
3117

32-
public function testParameterAsArray(): void
18+
/**
19+
* @param mixed ...$args
20+
*/
21+
public function testFileAsserts(
22+
string $assertType,
23+
string $file,
24+
...$args
25+
): void
3326
{
34-
$methodReflection = $this->createMock(MethodReflection::class);
35-
$methodReflection
36-
->method('getVariants')
37-
->willReturn([new FunctionVariant(
38-
TemplateTypeMap::createEmpty(),
39-
TemplateTypeMap::createEmpty(),
40-
[],
41-
true,
42-
new UnionType([new ArrayType(new MixedType(), new MixedType()), new IterableType(new MixedType(), new ObjectType(ArrayHash::class))]),
43-
)]);
44-
45-
$scope = $this->createMock(Scope::class);
46-
$scope->method('getType')->willReturn(new ConstantBooleanType(true));
47-
48-
$methodCall = $this->createMock(MethodCall::class);
49-
$arg = $this->createMock(Arg::class);
50-
$value = $this->createMock(Expr::class);
51-
$arg->value = $value;
52-
$methodCall->args = [
53-
0 => $arg,
54-
];
55-
$methodCall->method('getArgs')->willReturn($methodCall->args);
56-
57-
$resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
58-
59-
self::assertInstanceOf(ArrayType::class, $resultType);
27+
$this->assertFileAsserts($assertType, $file, ...$args);
6028
}
6129

62-
public function testParameterAsArrayHash(): void
30+
public static function getAdditionalConfigFiles(): array
6331
{
64-
$methodReflection = $this->createMock(MethodReflection::class);
65-
$methodReflection
66-
->method('getVariants')
67-
->willReturn([new FunctionVariant(TemplateTypeMap::createEmpty(), TemplateTypeMap::createEmpty(), [], true, new UnionType([new ArrayType(new MixedType(), new MixedType()), new IterableType(new MixedType(), new ObjectType(ArrayHash::class))]))]);
68-
69-
$scope = $this->createMock(Scope::class);
70-
$scope->method('getType')->willReturn(new ConstantBooleanType(false));
71-
72-
$methodCall = $this->createMock(MethodCall::class);
73-
$arg = $this->createMock(Arg::class);
74-
$value = $this->createMock(Expr::class);
75-
$arg->value = $value;
76-
$methodCall->args = [
77-
0 => $arg,
32+
return [
33+
__DIR__ . '/phpstan.neon',
7834
];
79-
$methodCall->method('getArgs')->willReturn($methodCall->args);
80-
81-
$resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
82-
83-
self::assertInstanceOf(ObjectType::class, $resultType);
84-
self::assertSame(ArrayHash::class, $resultType->describe(VerbosityLevel::value()));
85-
}
86-
87-
public function testDefaultParameterIsArrayHash(): void
88-
{
89-
$methodReflection = $this->createMock(MethodReflection::class);
90-
$methodReflection
91-
->method('getVariants')
92-
->willReturn([new FunctionVariant(TemplateTypeMap::createEmpty(), TemplateTypeMap::createEmpty(), [], true, new UnionType([new ArrayType(new MixedType(), new MixedType()), new IterableType(new MixedType(), new ObjectType(ArrayHash::class))]))]);
93-
94-
$scope = $this->createMock(Scope::class);
95-
$scope->method('getType')->willReturn(new ConstantBooleanType(false));
96-
97-
$methodCall = $this->createMock(MethodCall::class);
98-
$methodCall->args = [];
99-
$methodCall->method('getArgs')->willReturn($methodCall->args);
100-
101-
$resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
102-
103-
self::assertInstanceOf(ObjectType::class, $resultType);
104-
self::assertSame(ArrayHash::class, $resultType->describe(VerbosityLevel::value()));
10535
}
10636

10737
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace PHPStan\Type\Nette\Data\FormContainerModel;
4+
5+
use Nette\Forms\Form;
6+
use function PHPStan\Testing\assertType;
7+
8+
class Dto
9+
{
10+
public function __construct(
11+
public string $name,
12+
public string $value,
13+
)
14+
{
15+
}
16+
}
17+
18+
class FormContainerModel
19+
{
20+
public function test()
21+
{
22+
$form = new Form();
23+
$form->addText('name');
24+
$form->addText('value');
25+
26+
$dto = $form->getValues(Dto::class);
27+
$array = $form->getValues('array');
28+
$object = $form->getValues('object');
29+
30+
assertType(Dto::class, $dto);
31+
assertType('array', $array);
32+
assertType('object', $object);
33+
}
34+
}

0 commit comments

Comments
 (0)