Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ parameters:
- stubs/Application/UI/Presenter.stub
- stubs/Caching/Cache.stub
- stubs/ComponentModel/Component.stub
- stubs/ComponentModel/Container.stub
- stubs/ComponentModel/IComponent.stub
- stubs/ComponentModel/IContainer.stub
- stubs/Database/ResultSet.stub
Expand Down Expand Up @@ -127,3 +126,8 @@ services:
class: PHPStan\Type\Nette\StringsReplaceCallbackClosureTypeExtension
tags:
- phpstan.staticMethodParameterClosureTypeExtension

-
class: PHPStan\Stubs\Nette\StubFilesExtensionLoader
tags:
- phpstan.stubFilesExtension
47 changes: 47 additions & 0 deletions src/Stubs/Nette/StubFilesExtensionLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);

namespace PHPStan\Stubs\Nette;

use Composer\InstalledVersions;
use OutOfBoundsException;
use PHPStan\PhpDoc\StubFilesExtension;
use function class_exists;
use function dirname;
use function version_compare;

class StubFilesExtensionLoader implements StubFilesExtension
{

public function getFiles(): array
{
$stubsDir = dirname(dirname(dirname(__DIR__))) . '/stubs';
$path = $stubsDir;

$files = [];

$componentModelVersion = self::getInstalledVersion('nette/component-model');
if ($componentModelVersion !== null && version_compare($componentModelVersion, '3.1.0', '>=')) {
$files[] = $path . '/ComponentModel/Container_3_1.stub';
} else {
$files[] = $path . '/ComponentModel/Container.stub';
}

return $files;
}

private static function getInstalledVersion(string $package): ?string
{
if (!class_exists(InstalledVersions::class)) {
return null;
}

try {
$installedVersion = InstalledVersions::getVersion($package);
} catch (OutOfBoundsException $e) {
return null;
}

return $installedVersion;
}

}
2 changes: 1 addition & 1 deletion stubs/ComponentModel/Container.stub
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Container extends Component implements IContainer
* @phpstan-param null|class-string<T> $filterType
* @phpstan-return ($filterType is null ? \Iterator<int|string, \Nette\ComponentModel\IComponent> : \Iterator<int|string, T>)
*/
public function getComponents(bool $deep = false, string $filterType = null): \Iterator
public function getComponents(bool $deep = false, ?string $filterType = null): \Iterator
{
// nothing
}
Expand Down
21 changes: 21 additions & 0 deletions stubs/ComponentModel/Container_3_1.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Nette\ComponentModel;

class Container extends Component implements IContainer
{

/**
* @template T of \Nette\ComponentModel\IComponent
* @phpstan-param null|class-string<T> $filterType
* @phpstan-return (
* $deep is false
* ? ($filterType is null ? array<int|string, \Nette\ComponentModel\IComponent> : array<int|string, T>)
* : ($filterType is null ? \Iterator<int|string, \Nette\ComponentModel\IComponent> : \Iterator<int|string, T>)
* )
*/
public function getComponents(bool $deep = false, ?string $filterType = null): iterable
{
// nothing
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Nette;

use Composer\InstalledVersions;
use OutOfBoundsException;
use PHPStan\Testing\TypeInferenceTestCase;
use function class_exists;
use function version_compare;

class ComponentModelContainerDynamicReturnTypeExtensionTest extends TypeInferenceTestCase
{

/**
* @return iterable<string, mixed[]>
*/
public function dataFileAsserts(): iterable
{
$componentModelVersion = self::getInstalledVersion('nette/component-model');

$suffix = $componentModelVersion !== null && version_compare($componentModelVersion, '3.1.0', '>=') ? '31' : '';

yield from self::gatherAssertTypes(__DIR__ . '/data/componentModelContainer' . $suffix . '.php');
}

/**
* @dataProvider dataFileAsserts
* @param mixed ...$args
*/
public function testFileAsserts(
string $assertType,
string $file,
...$args
): void
{
$this->assertFileAsserts($assertType, $file, ...$args);
}

public static function getAdditionalConfigFiles(): array
{
return [
__DIR__ . '/phpstan.neon',
];
}

private static function getInstalledVersion(string $package): ?string
{
if (!class_exists(InstalledVersions::class)) {
return null;
}

try {
$installedVersion = InstalledVersions::getVersion($package);
} catch (OutOfBoundsException $e) {
return null;
}

return $installedVersion;
}

}
17 changes: 17 additions & 0 deletions tests/Type/Nette/data/componentModelContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace PHPStan\Type\Nette\Data\ComponentModel;

use Nette\Application\UI\Form;
use Nette\Forms\Container;
use function PHPStan\Testing\assertType;

class SomeForm extends Form {
}

$someForm = new SomeForm();

assertType('Iterator<int|string, Nette\ComponentModel\IComponent>', $someForm->getComponents(false));
assertType('Iterator<int|string, Nette\Forms\Container>', $someForm->getComponents(false, Container::class));
assertType('Iterator<int|string, Nette\ComponentModel\IComponent>', $someForm->getComponents(true));
assertType('Iterator<int|string, Nette\Forms\Container>', $someForm->getComponents(true, Container::class));
17 changes: 17 additions & 0 deletions tests/Type/Nette/data/componentModelContainer31.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace PHPStan\Type\Nette\Data\ComponentModel;

use Nette\Application\UI\Form;
use Nette\Forms\Container;
use function PHPStan\Testing\assertType;

class SomeForm extends Form {
}

$someForm = new SomeForm();

assertType('array<int|string, Nette\ComponentModel\IComponent>', $someForm->getComponents(false));
assertType('array<int|string, Nette\Forms\Container>', $someForm->getComponents(false, Container::class));
assertType('Iterator<int|string, Nette\ComponentModel\IComponent>', $someForm->getComponents(true));
assertType('Iterator<int|string, Nette\Forms\Container>', $someForm->getComponents(true, Container::class));
Loading