Infer type within foreach

parent 951eb169
......@@ -120,7 +120,9 @@ trait NodeTypeManagerTrait
StaticLoggerFacade::debug($message);
return $node->getAttribute(ResolverCollection::getName())->resolve();
$resolverCollection = $node->getAttribute(ResolverCollection::getName());
return $resolverCollection->resolve();
}
private static function getNodeResolverCollection(Node $node): ResolverCollection
......
......@@ -81,6 +81,7 @@ final class TypeResolverInjectorVisitor extends NodeVisitorAbstract
* @uses addPropertyFetchTypeResolver
* @uses addPropertyTypeResolver
* @uses addUseUseTypeResolver
* @uses addForeachTypeResolver
*/
public function enterNode(Node $node): void
{
......@@ -223,6 +224,12 @@ final class TypeResolverInjectorVisitor extends NodeVisitorAbstract
}
}
private function addForeachTypeResolver(Foreach_ $foreachNode): void
{
$this->assignTypeToForeachKey($foreachNode->keyVar);
$this->assignTypeToForeachVar($foreachNode->expr, $foreachNode->valueVar);
}
private function addVariableTypeResolver(Variable $variableNode): void
{
if ($variableNode->name === 'this') {
......@@ -438,7 +445,7 @@ final class TypeResolverInjectorVisitor extends NodeVisitorAbstract
private function getResolverAdderName(Node $node): string
{
return 'add'
. ClassHelper::extractCanonicalClassName(get_class($node))
. rtrim(ClassHelper::extractCanonicalClassName(get_class($node)), '_')
. 'TypeResolver';
}
......@@ -508,4 +515,63 @@ final class TypeResolverInjectorVisitor extends NodeVisitorAbstract
return $this->typeFactory->buildTypeFromString($namespacedType);
}
private function assignTypeToForeachKey(Expr $keyVar): void
{
if ($keyVar === null) {
return;
}
$resolver = function (): TypeCollection {
return new TypeCollection(new Type('string'), new Type('int'));
};
self::addTypeResolver($keyVar, $resolver);
if ($keyVar instanceof Variable) {
// Assignment to variable
$this->variableCollector->resetResolverCollection($this->getVariableName($keyVar), $resolver);
} elseif ($keyVar instanceof PropertyFetch) {
// Assignment to property
$this->propertyCollector->collectResolver($this->getPropertyName($keyVar), $resolver);
}
}
private function assignTypeToForeachVar(Expr $expression, Expr $valueVar): void
{
$resolver = function () use ($expression): TypeCollection {
try {
/** @var Type[] $typeCollection */
$typeCollection = self::resolveType($expression);
$nestedTypeCollection = new TypeCollection();
foreach ($typeCollection as $type) {
if ($type->hasNestedType()) {
$nestedTypeCollection = $nestedTypeCollection->addType($type->getNestedType());
}
}
return $nestedTypeCollection;
} catch (UnresolvableNodeTypeException $e) {
StaticLoggerFacade::warning(
"Silently ignoring a UnresolvableNodeTypeException in this filter.\n"
. 'This is failing, at least, for nested method calls like'
. '`$invoice->transactions->first()->getServicePro();`.' . "\n"
. "This should be fixed in the type addition visitors.\n"
. $e->getMessage(),
[__METHOD__]
);
return new TypeCollection();
}
};
self::addTypeResolver($valueVar, $resolver);
if ($valueVar instanceof Variable) {
// Assignment to variable
$this->variableCollector->resetResolverCollection($this->getVariableName($valueVar), $resolver);
} elseif ($valueVar instanceof PropertyFetch) {
// Assignment to property
$this->propertyCollector->collectResolver($this->getPropertyName($valueVar), $resolver);
}
}
}
......@@ -17,6 +17,7 @@ declare(strict_types=1);
namespace Hgraca\AppMapper\Test\StubProjectSrc\Core\Component\X\Application\Service;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\Port\EventDispatcher\EventInterface;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\SharedKernel\Event\CccEvent;
use Hgraca\AppMapper\Test\StubProjectSrc\Core\SharedKernel\Event\DddEvent;
......@@ -27,6 +28,11 @@ final class XxxBbbService
*/
private $xxxAaaService;
/**
* @var EventInterface[]
*/
private $eventList;
public function __construct(XxxAaaService $xxxAaaService)
{
$this->xxxAaaService = $xxxAaaService;
......@@ -56,4 +62,11 @@ final class XxxBbbService
{
return CccEvent::namedConstructor();
}
public function testForeach(): void
{
foreach ($this->eventList as $key => $event) {
$otherVariable = $event;
}
}
}
......@@ -594,6 +594,42 @@ final class NodeCollectionIntegrationTest extends AbstractIntegrationTest
);
}
/**
* @test
*
* @throws \ReflectionException
*/
public function foreach_assigns_array_nested_types_to_variable(): void
{
$methodNode = $this->getMethod('testForeach', XxxBbbService::class);
self::assertArrayHasKey(
'array',
ReflectionHelper::getNestedProperty(
'stmts.0.expr.attributes.TypeCollection.itemList',
$methodNode
)
);
self::assertEquals(
EventInterface::class,
ReflectionHelper::getNestedProperty(
'stmts.0.expr.attributes.TypeCollection.itemList.array.nestedType.typeAsString',
$methodNode
)
);
self::assertArrayHasKey(
'int',
ReflectionHelper::getNestedProperty('stmts.0.keyVar.attributes.TypeCollection.itemList', $methodNode)
);
self::assertArrayHasKey(
'string',
ReflectionHelper::getNestedProperty('stmts.0.keyVar.attributes.TypeCollection.itemList', $methodNode)
);
self::assertArrayHasKey(
EventInterface::class,
ReflectionHelper::getNestedProperty('stmts.0.valueVar.attributes.TypeCollection.itemList', $methodNode)
);
}
private function getProperty(string $propertyName, string $classFqcn): Property
{
/** @var Class_ $classNode */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment