Skip to content

Commit

Permalink
Add SimpleXmlElementXpathMethodReturnTypeExtension
Browse files Browse the repository at this point in the history
  • Loading branch information
VincentLanglet authored and ondrejmirtes committed Jan 24, 2021
1 parent 863e6c6 commit e5bac6e
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 0 deletions.
5 changes: 5 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,11 @@ services:
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

-
class: PHPStan\Type\Php\SimpleXMLElementXpathMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

-
class: PHPStan\Type\Php\StrSplitFunctionReturnTypeExtension
tags:
Expand Down
63 changes: 63 additions & 0 deletions src/Type/Php/SimpleXMLElementXpathMethodReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;

class SimpleXMLElementXpathMethodReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension
{

public function getClass(): string
{
return \SimpleXMLElement::class;
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'xpath';
}

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
if (!isset($methodCall->args[0])) {
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

$argType = $scope->getType($methodCall->args[0]->value);

$xmlElement = new \SimpleXMLElement('<foo />');

$result = null;
foreach (TypeUtils::getConstantStrings($argType) as $constantString) {
$newResult = @$xmlElement->xpath($constantString->getValue());

if ($result !== null && gettype($result) !== gettype($newResult)) {
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

$result = $newResult;
$argType = TypeCombinator::remove($argType, $constantString);
}

if ($result === null || !$argType instanceof NeverType) {
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

if ($result === false) {
return new ConstantBooleanType(false);
}

return new ArrayType(new MixedType(), $scope->getType($methodCall->var));
}

}
12 changes: 12 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3226,6 +3226,18 @@ public function dataBinaryOperations(): array
'bool',
'$simpleXMLWritingXML',
],
[
'array<SimpleXMLElement>',
'$simpleXMLRightXpath',
],
[
'false',
'$simpleXMLWrongXpath',
],
[
'array<static(SimpleXMLElement)>|false',
'$simpleXMLUnknownXpath',
],
];
}

Expand Down
7 changes: 7 additions & 0 deletions tests/PHPStan/Analyser/data/binary.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ public function doFoo(array $generalArray)

$simpleXMLWritingXML = $simpleXML->asXML('path.xml');

/** @var string $stringForXpath */
$stringForXpath = doFoo();

$simpleXMLRightXpath = $simpleXML->xpath('/a/b/c');
$simpleXMLWrongXpath = $simpleXML->xpath('[foo]');
$simpleXMLUnknownXpath = $simpleXML->xpath($stringForXpath);

if (rand(0, 1)) {
$maybeDefinedVariable = 'foo';
}
Expand Down

0 comments on commit e5bac6e

Please sign in to comment.