Skip to content

Commit 157a414

Browse files
MartinMystikJonasondrejmirtes
authored andcommitted
Fixed component model return type extensions for cases where createComponent method is not present
1 parent 23eac57 commit 157a414

7 files changed

+181
-10
lines changed

Diff for: src/Type/Nette/ComponentModelArrayAccessDynamicReturnTypeExtension.php

+4-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use PHPStan\Reflection\MethodReflection;
88
use PHPStan\Reflection\ParametersAcceptorSelector;
99
use PHPStan\Type\DynamicMethodReturnTypeExtension;
10-
use PHPStan\Type\MixedType;
1110
use PHPStan\Type\Type;
1211
use PHPStan\Type\TypeCombinator;
1312
use function count;
@@ -36,15 +35,15 @@ public function getTypeFromMethodCall(
3635
): Type
3736
{
3837
$calledOnType = $scope->getType($methodCall->var);
39-
$mixedType = new MixedType();
38+
$defaultType = ParametersAcceptorSelector::selectSingle($calledOnType->getMethod('createComponent', $scope)->getVariants())->getReturnType();
4039
$args = $methodCall->getArgs();
4140
if (count($args) !== 1) {
42-
return $mixedType;
41+
return $defaultType;
4342
}
4443

4544
$argType = $scope->getType($args[0]->value);
4645
if (count($argType->getConstantStrings()) === 0) {
47-
return $mixedType;
46+
return $defaultType;
4847
}
4948

5049
$types = [];
@@ -53,7 +52,7 @@ public function getTypeFromMethodCall(
5352

5453
$methodName = sprintf('createComponent%s', ucfirst($componentName));
5554
if (!$calledOnType->hasMethod($methodName)->yes()) {
56-
return $mixedType;
55+
return $defaultType;
5756
}
5857

5958
$method = $calledOnType->getMethod($methodName, $scope);

Diff for: src/Type/Nette/ComponentModelDynamicReturnTypeExtension.php

+4-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use PHPStan\Reflection\MethodReflection;
88
use PHPStan\Reflection\ParametersAcceptorSelector;
99
use PHPStan\Type\DynamicMethodReturnTypeExtension;
10-
use PHPStan\Type\MixedType;
1110
use PHPStan\Type\Type;
1211
use PHPStan\Type\TypeCombinator;
1312
use function count;
@@ -36,15 +35,15 @@ public function getTypeFromMethodCall(
3635
): Type
3736
{
3837
$calledOnType = $scope->getType($methodCall->var);
39-
$mixedType = new MixedType();
38+
$defaultType = ParametersAcceptorSelector::selectSingle($calledOnType->getMethod('createComponent', $scope)->getVariants())->getReturnType();
4039
$args = $methodCall->getArgs();
4140
if (count($args) !== 1) {
42-
return $mixedType;
41+
return $defaultType;
4342
}
4443

4544
$argType = $scope->getType($args[0]->value);
4645
if (count($argType->getConstantStrings()) === 0) {
47-
return $mixedType;
46+
return $defaultType;
4847
}
4948

5049
$types = [];
@@ -53,7 +52,7 @@ public function getTypeFromMethodCall(
5352

5453
$methodName = sprintf('createComponent%s', ucfirst($componentName));
5554
if (!$calledOnType->hasMethod($methodName)->yes()) {
56-
return $mixedType;
55+
return $defaultType;
5756
}
5857

5958
$method = $calledOnType->getMethod($methodName, $scope);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Nette;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
7+
class ComponentModelArrayAccessDynamicReturnTypeExtensionTest extends TypeInferenceTestCase
8+
{
9+
10+
/**
11+
* @return iterable<string, mixed[]>
12+
*/
13+
public function dataFileAsserts(): iterable
14+
{
15+
yield from $this->gatherAssertTypes(__DIR__ . '/data/componentModelArrayAccess.php');
16+
}
17+
18+
/**
19+
* @dataProvider dataFileAsserts
20+
* @param mixed ...$args
21+
*/
22+
public function testFileAsserts(
23+
string $assertType,
24+
string $file,
25+
...$args
26+
): void
27+
{
28+
$this->assertFileAsserts($assertType, $file, ...$args);
29+
}
30+
31+
public static function getAdditionalConfigFiles(): array
32+
{
33+
return [
34+
__DIR__ . '/phpstan.neon',
35+
];
36+
}
37+
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Nette;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
7+
class ComponentModelDynamicReturnTypeExtensionTest extends TypeInferenceTestCase
8+
{
9+
10+
/**
11+
* @return iterable<string, mixed[]>
12+
*/
13+
public function dataFileAsserts(): iterable
14+
{
15+
yield from $this->gatherAssertTypes(__DIR__ . '/data/componentModel.php');
16+
}
17+
18+
/**
19+
* @dataProvider dataFileAsserts
20+
* @param mixed ...$args
21+
*/
22+
public function testFileAsserts(
23+
string $assertType,
24+
string $file,
25+
...$args
26+
): void
27+
{
28+
$this->assertFileAsserts($assertType, $file, ...$args);
29+
}
30+
31+
public static function getAdditionalConfigFiles(): array
32+
{
33+
return [
34+
__DIR__ . '/phpstan.neon',
35+
];
36+
}
37+
38+
}

Diff for: tests/Type/Nette/data/componentModel.php

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace PHPStan\Type\Nette\Data\ComponentModel;
4+
5+
use Nette\Application\UI\Control;
6+
use function PHPStan\Testing\assertType;
7+
8+
class SomeControl extends Control {
9+
10+
public function createComponentSome(): self {
11+
return new SomeControl();
12+
}
13+
14+
}
15+
16+
class AnotherControl extends Control {
17+
18+
public function createComponentAnother(): AnotherControl {
19+
return new AnotherControl();
20+
}
21+
22+
public function createComponentSome(): SomeControl {
23+
return new SomeControl();
24+
}
25+
26+
}
27+
28+
class OverrideCreateControl extends Control {
29+
30+
public function createComponent(string $name): AnotherControl {
31+
return new AnotherControl();
32+
}
33+
34+
}
35+
36+
$someControl = new SomeControl();
37+
assertType('PHPStan\Type\Nette\Data\ComponentModel\SomeControl', $someControl->getComponent('some'));
38+
assertType('Nette\ComponentModel\IComponent|null', $someControl->getComponent('unknown'));
39+
40+
$anotherControl = new AnotherControl();
41+
assertType('PHPStan\Type\Nette\Data\ComponentModel\AnotherControl', $anotherControl->getComponent('another'));
42+
assertType('PHPStan\Type\Nette\Data\ComponentModel\SomeControl', $anotherControl->getComponent('some'));
43+
assertType('Nette\ComponentModel\IComponent|null', $anotherControl->getComponent('unknown'));
44+
45+
$overrideCreateControl = new OverrideCreateControl();
46+
assertType('PHPStan\Type\Nette\Data\ComponentModel\AnotherControl', $overrideCreateControl->getComponent('some'));
47+
assertType('PHPStan\Type\Nette\Data\ComponentModel\AnotherControl', $overrideCreateControl->getComponent('unknown'));

Diff for: tests/Type/Nette/data/componentModelArrayAccess.php

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace PHPStan\Type\Nette\Data\ComponentModelArrayAccess;
4+
5+
use Nette\Application\UI\Control;
6+
use function PHPStan\Testing\assertType;
7+
8+
class SomeControl extends Control {
9+
10+
public function createComponentSome(): self {
11+
return new SomeControl();
12+
}
13+
14+
}
15+
16+
class AnotherControl extends Control {
17+
18+
public function createComponentAnother(): AnotherControl {
19+
return new AnotherControl();
20+
}
21+
22+
public function createComponentSome(): SomeControl {
23+
return new SomeControl();
24+
}
25+
26+
}
27+
28+
class OverrideCreateControl extends Control {
29+
30+
public function createComponent(string $name): AnotherControl {
31+
return new AnotherControl();
32+
}
33+
34+
}
35+
36+
$someControl = new SomeControl();
37+
assertType('PHPStan\Type\Nette\Data\ComponentModelArrayAccess\SomeControl', $someControl['some']);
38+
assertType('Nette\ComponentModel\IComponent|null', $someControl['unknown']);
39+
40+
$anotherControl = new AnotherControl();
41+
assertType('PHPStan\Type\Nette\Data\ComponentModelArrayAccess\AnotherControl', $anotherControl['another']);
42+
assertType('PHPStan\Type\Nette\Data\ComponentModelArrayAccess\SomeControl', $anotherControl['some']);
43+
assertType('Nette\ComponentModel\IComponent|null', $anotherControl['unknown']);
44+
45+
$overrideCreateControl = new OverrideCreateControl();
46+
assertType('PHPStan\Type\Nette\Data\ComponentModelArrayAccess\AnotherControl', $overrideCreateControl['some']);
47+
assertType('PHPStan\Type\Nette\Data\ComponentModelArrayAccess\AnotherControl', $overrideCreateControl['unknown']);

Diff for: tests/Type/Nette/phpstan.neon

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
includes:
2+
- ../../../extension.neon
3+
- ../../../rules.neon

0 commit comments

Comments
 (0)