From 74694e3c9e287133f92d6ec9614ed734ba42d46f Mon Sep 17 00:00:00 2001 From: Jiri Velek Date: Mon, 15 Sep 2025 17:38:55 +0200 Subject: [PATCH] fix getValues() invalid type --- ...tainerValuesDynamicReturnTypeExtension.php | 30 +++-- ...erValuesDynamicReturnTypeExtensionTest.php | 109 ++++-------------- tests/Type/Nette/data/FormContainerModel.php | 37 ++++++ 3 files changed, 79 insertions(+), 97 deletions(-) create mode 100644 tests/Type/Nette/data/FormContainerModel.php diff --git a/src/Type/Nette/FormContainerValuesDynamicReturnTypeExtension.php b/src/Type/Nette/FormContainerValuesDynamicReturnTypeExtension.php index d4bf152..e864c3e 100644 --- a/src/Type/Nette/FormContainerValuesDynamicReturnTypeExtension.php +++ b/src/Type/Nette/FormContainerValuesDynamicReturnTypeExtension.php @@ -26,22 +26,36 @@ public function isMethodSupported(MethodReflection $methodReflection): bool return $methodReflection->getName() === 'getValues'; } - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type { - if (count($methodCall->getArgs()) === 0) { + $args = $methodCall->getArgs(); + + if (count($args) === 0) { return new ObjectType('Nette\Utils\ArrayHash'); } - $arg = $methodCall->getArgs()[0]->value; + $arg = $args[0]->value; $scopedType = $scope->getType($arg); - if ($scopedType->isTrue()->yes()) { - return new ArrayType(new StringType(), new MixedType()); - } - if ($scopedType->isFalse()->yes()) { + + $constantStrings = $scopedType->getConstantStrings(); + + if (count($constantStrings) === 0) { return new ObjectType('Nette\Utils\ArrayHash'); } - return null; + $constantString = $constantStrings[0]; + + $value = $constantString->getValue(); + + if ($scopedType->isClassString()->yes()) { + return $scopedType->getClassStringObjectType(); + } + + if ($value === 'array') { + return new ArrayType(new StringType(), new MixedType()); + } + + return new ObjectType('Nette\Utils\ArrayHash'); } } diff --git a/tests/Type/Nette/FormContainerValuesDynamicReturnTypeExtensionTest.php b/tests/Type/Nette/FormContainerValuesDynamicReturnTypeExtensionTest.php index c262d22..fa9aac7 100644 --- a/tests/Type/Nette/FormContainerValuesDynamicReturnTypeExtensionTest.php +++ b/tests/Type/Nette/FormContainerValuesDynamicReturnTypeExtensionTest.php @@ -2,106 +2,37 @@ namespace PHPStan\Type\Nette; -use Nette\Utils\ArrayHash; -use PhpParser\Node\Arg; -use PhpParser\Node\Expr; -use PhpParser\Node\Expr\MethodCall; -use PHPStan\Analyser\Scope; -use PHPStan\Reflection\FunctionVariant; -use PHPStan\Reflection\MethodReflection; -use PHPStan\Type\ArrayType; -use PHPStan\Type\Constant\ConstantBooleanType; -use PHPStan\Type\Generic\TemplateTypeMap; -use PHPStan\Type\IterableType; -use PHPStan\Type\MixedType; -use PHPStan\Type\ObjectType; -use PHPStan\Type\UnionType; -use PHPStan\Type\VerbosityLevel; -use PHPUnit\Framework\TestCase; +use PHPStan\Testing\TypeInferenceTestCase; -final class FormContainerValuesDynamicReturnTypeExtensionTest extends TestCase +final class FormContainerValuesDynamicReturnTypeExtensionTest extends TypeInferenceTestCase { - private FormContainerValuesDynamicReturnTypeExtension $extension; - - protected function setUp(): void + /** + * @return iterable + */ + public static function dataFileAsserts(): iterable { - $this->extension = new FormContainerValuesDynamicReturnTypeExtension(); + yield from self::gatherAssertTypes(__DIR__ . '/data/FormContainerModel.php'); } - public function testParameterAsArray(): void + /** + * @dataProvider dataFileAsserts + * @param mixed ...$args + */ + public function testFileAsserts( + string $assertType, + string $file, + ...$args + ): void { - $methodReflection = $this->createMock(MethodReflection::class); - $methodReflection - ->method('getVariants') - ->willReturn([new FunctionVariant( - TemplateTypeMap::createEmpty(), - TemplateTypeMap::createEmpty(), - [], - true, - new UnionType([new ArrayType(new MixedType(), new MixedType()), new IterableType(new MixedType(), new ObjectType(ArrayHash::class))]), - )]); - - $scope = $this->createMock(Scope::class); - $scope->method('getType')->willReturn(new ConstantBooleanType(true)); - - $methodCall = $this->createMock(MethodCall::class); - $arg = $this->createMock(Arg::class); - $value = $this->createMock(Expr::class); - $arg->value = $value; - $methodCall->args = [ - 0 => $arg, - ]; - $methodCall->method('getArgs')->willReturn($methodCall->args); - - $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope); - - self::assertInstanceOf(ArrayType::class, $resultType); + $this->assertFileAsserts($assertType, $file, ...$args); } - public function testParameterAsArrayHash(): void + public static function getAdditionalConfigFiles(): array { - $methodReflection = $this->createMock(MethodReflection::class); - $methodReflection - ->method('getVariants') - ->willReturn([new FunctionVariant(TemplateTypeMap::createEmpty(), TemplateTypeMap::createEmpty(), [], true, new UnionType([new ArrayType(new MixedType(), new MixedType()), new IterableType(new MixedType(), new ObjectType(ArrayHash::class))]))]); - - $scope = $this->createMock(Scope::class); - $scope->method('getType')->willReturn(new ConstantBooleanType(false)); - - $methodCall = $this->createMock(MethodCall::class); - $arg = $this->createMock(Arg::class); - $value = $this->createMock(Expr::class); - $arg->value = $value; - $methodCall->args = [ - 0 => $arg, + return [ + __DIR__ . '/phpstan.neon', ]; - $methodCall->method('getArgs')->willReturn($methodCall->args); - - $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope); - - self::assertInstanceOf(ObjectType::class, $resultType); - self::assertSame(ArrayHash::class, $resultType->describe(VerbosityLevel::value())); - } - - public function testDefaultParameterIsArrayHash(): void - { - $methodReflection = $this->createMock(MethodReflection::class); - $methodReflection - ->method('getVariants') - ->willReturn([new FunctionVariant(TemplateTypeMap::createEmpty(), TemplateTypeMap::createEmpty(), [], true, new UnionType([new ArrayType(new MixedType(), new MixedType()), new IterableType(new MixedType(), new ObjectType(ArrayHash::class))]))]); - - $scope = $this->createMock(Scope::class); - $scope->method('getType')->willReturn(new ConstantBooleanType(false)); - - $methodCall = $this->createMock(MethodCall::class); - $methodCall->args = []; - $methodCall->method('getArgs')->willReturn($methodCall->args); - - $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope); - - self::assertInstanceOf(ObjectType::class, $resultType); - self::assertSame(ArrayHash::class, $resultType->describe(VerbosityLevel::value())); } } diff --git a/tests/Type/Nette/data/FormContainerModel.php b/tests/Type/Nette/data/FormContainerModel.php new file mode 100644 index 0000000..6a0f65b --- /dev/null +++ b/tests/Type/Nette/data/FormContainerModel.php @@ -0,0 +1,37 @@ +name = $name; + $this->name = $value; + } +} + +class FormContainerModel +{ + public function test() + { + $form = new Form(); + $form->addText('name'); + $form->addText('value'); + + $dto = $form->getValues(Dto::class); + $array = $form->getValues('array'); + + assertType(Dto::class, $dto); + assertType('array', $array); + } +}