Skip to content

Commit 90ed4f1

Browse files
Bleksakondrejmirtes
authored andcommitted
fix getValues() invalid type
1 parent ad594d4 commit 90ed4f1

File tree

3 files changed

+79
-97
lines changed

3 files changed

+79
-97
lines changed

src/Type/Nette/FormContainerValuesDynamicReturnTypeExtension.php

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,36 @@ 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+
if (count($args) === 0) {
3234
return new ObjectType('Nette\Utils\ArrayHash');
3335
}
3436

35-
$arg = $methodCall->getArgs()[0]->value;
37+
$arg = $args[0]->value;
3638
$scopedType = $scope->getType($arg);
37-
if ($scopedType->isTrue()->yes()) {
38-
return new ArrayType(new StringType(), new MixedType());
39-
}
40-
if ($scopedType->isFalse()->yes()) {
39+
40+
$constantStrings = $scopedType->getConstantStrings();
41+
42+
if (count($constantStrings) === 0) {
4143
return new ObjectType('Nette\Utils\ArrayHash');
4244
}
4345

44-
return null;
46+
$constantString = $constantStrings[0];
47+
48+
$value = $constantString->getValue();
49+
50+
if ($scopedType->isClassString()->yes()) {
51+
return $scopedType->getClassStringObjectType();
52+
}
53+
54+
if ($value === 'array') {
55+
return new ArrayType(new StringType(), new MixedType());
56+
}
57+
58+
return new ObjectType('Nette\Utils\ArrayHash');
4559
}
4660

4761
}

tests/Type/Nette/FormContainerValuesDynamicReturnTypeExtensionTest.php

Lines changed: 20 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,106 +2,37 @@
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+
* @dataProvider dataFileAsserts
20+
* @param mixed ...$args
21+
*/
22+
public function testFileAsserts(
23+
string $assertType,
24+
string $file,
25+
...$args
26+
): void
3327
{
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);
28+
$this->assertFileAsserts($assertType, $file, ...$args);
6029
}
6130

62-
public function testParameterAsArrayHash(): void
31+
public static function getAdditionalConfigFiles(): array
6332
{
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,
33+
return [
34+
__DIR__ . '/phpstan.neon',
7835
];
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()));
10536
}
10637

10738
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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 string $name;
11+
public string $value;
12+
13+
public function __construct(
14+
string $name,
15+
string $value
16+
)
17+
{
18+
$this->name = $name;
19+
$this->name = $value;
20+
}
21+
}
22+
23+
class FormContainerModel
24+
{
25+
public function test()
26+
{
27+
$form = new Form();
28+
$form->addText('name');
29+
$form->addText('value');
30+
31+
$dto = $form->getValues(Dto::class);
32+
$array = $form->getValues('array');
33+
34+
assertType(Dto::class, $dto);
35+
assertType('array<string, mixed>', $array);
36+
}
37+
}

0 commit comments

Comments
 (0)