Navigation Menu

Skip to content

Commit

Permalink
Support define() and defined() in local scope
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 30, 2018
1 parent ffd57a3 commit a4e248e
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 30 deletions.
10 changes: 10 additions & 0 deletions conf/config.neon
Expand Up @@ -352,6 +352,16 @@ services:
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension

-
class: PHPStan\Type\Php\DefineConstantTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension

-
class: PHPStan\Type\Php\DefinedConstantTypeSpecifyingExtension
tags:
- phpstan.typeSpecifier.functionTypeSpecifyingExtension

-
class: PHPStan\Type\Php\IsIntFunctionTypeSpecifyingExtension
tags:
Expand Down
9 changes: 9 additions & 0 deletions src/Analyser/Scope.php
Expand Up @@ -318,6 +318,15 @@ private function isGlobalVariable(string $variableName): bool
], true);
}

public function hasConstant(Name $name): bool
{
$node = new ConstFetch(new Name\FullyQualified($name->toCodeString()));
if ($this->isSpecified($node)) {
return true;
}
return $this->broker->hasConstant($name, $this);
}

public function isInAnonymousFunction(): bool
{
return $this->inAnonymousFunctionReturnType !== null;
Expand Down
24 changes: 15 additions & 9 deletions src/Rules/Comparison/ImpossibleCheckTypeHelper.php
Expand Up @@ -33,19 +33,25 @@ public function findSpecifiedType(
{
if (
$node instanceof FuncCall
&& $node->name instanceof \PhpParser\Node\Name
&& strtolower((string) $node->name) === 'is_numeric'
&& count($node->args) > 0
) {
$argType = $scope->getType($node->args[0]->value);
if (count(\PHPStan\Type\TypeUtils::getConstantScalars($argType)) > 0) {
return !$argType->toNumber() instanceof ErrorType;
}

if (!(new StringType())->isSuperTypeOf($argType)->no()) {
return null;
if ($node->name instanceof \PhpParser\Node\Name) {
$functionName = strtolower((string) $node->name);
if ($functionName === 'is_numeric') {
$argType = $scope->getType($node->args[0]->value);
if (count(\PHPStan\Type\TypeUtils::getConstantScalars($argType)) > 0) {
return !$argType->toNumber() instanceof ErrorType;
}

if (!(new StringType())->isSuperTypeOf($argType)->no()) {
return null;
}
} elseif ($functionName === 'defined') {
return null;
}
}
}

$specifiedTypes = $this->typeSpecifier->specifyTypesInCondition($scope, $node, TypeSpecifierContext::createTruthy());
$sureTypes = $specifiedTypes->getSureTypes();
$sureNotTypes = $specifiedTypes->getSureNotTypes();
Expand Down
11 changes: 1 addition & 10 deletions src/Rules/Constants/ConstantRule.php
Expand Up @@ -4,19 +4,10 @@

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Broker\Broker;

class ConstantRule implements \PHPStan\Rules\Rule
{

/** @var \PHPStan\Broker\Broker */
private $broker;

public function __construct(Broker $broker)
{
$this->broker = $broker;
}

public function getNodeType(): string
{
return Node\Expr\ConstFetch::class;
Expand All @@ -29,7 +20,7 @@ public function getNodeType(): string
*/
public function processNode(Node $node, Scope $scope): array
{
if (!$this->broker->hasConstant($node->name, $scope)) {
if (!$scope->hasConstant($node->name)) {
return [
sprintf(
'Constant %s not found.',
Expand Down
61 changes: 61 additions & 0 deletions src/Type/Php/DefineConstantTypeSpecifyingExtension.php
@@ -0,0 +1,61 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\FunctionTypeSpecifyingExtension;

class DefineConstantTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension
{

/** @var TypeSpecifier */
private $typeSpecifier;

public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
{
$this->typeSpecifier = $typeSpecifier;
}

public function isFunctionSupported(
FunctionReflection $functionReflection,
FuncCall $node,
TypeSpecifierContext $context
): bool
{
return $functionReflection->getName() === 'define'
&& $context->null()
&& count($node->args) >= 2;
}

public function specifyTypes(
FunctionReflection $functionReflection,
FuncCall $node,
Scope $scope,
TypeSpecifierContext $context
): SpecifiedTypes
{
$constantName = $scope->getType($node->args[0]->value);
if (
!$constantName instanceof ConstantStringType
|| $constantName->getValue() === ''
) {
return new SpecifiedTypes([], []);
}

return $this->typeSpecifier->create(
new \PhpParser\Node\Expr\ConstFetch(
new \PhpParser\Node\Name\FullyQualified($constantName->getValue())
),
$scope->getType($node->args[1]->value),
TypeSpecifierContext::createTruthy()
);
}

}
62 changes: 62 additions & 0 deletions src/Type/Php/DefinedConstantTypeSpecifyingExtension.php
@@ -0,0 +1,62 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\FunctionTypeSpecifyingExtension;
use PHPStan\Type\MixedType;

class DefinedConstantTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension
{

/** @var TypeSpecifier */
private $typeSpecifier;

public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
{
$this->typeSpecifier = $typeSpecifier;
}

public function isFunctionSupported(
FunctionReflection $functionReflection,
FuncCall $node,
TypeSpecifierContext $context
): bool
{
return $functionReflection->getName() === 'defined'
&& count($node->args) >= 1
&& !$context->null();
}

public function specifyTypes(
FunctionReflection $functionReflection,
FuncCall $node,
Scope $scope,
TypeSpecifierContext $context
): SpecifiedTypes
{
$constantName = $scope->getType($node->args[0]->value);
if (
!$constantName instanceof ConstantStringType
|| $constantName->getValue() === ''
) {
return new SpecifiedTypes([], []);
}

return $this->typeSpecifier->create(
new \PhpParser\Node\Expr\ConstFetch(
new \PhpParser\Node\Name\FullyQualified($constantName->getValue())
),
new MixedType(),
$context
);
}

}
23 changes: 14 additions & 9 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -2731,6 +2731,10 @@ public function testLiteralArrays(

public function dataLiteralArraysKeys(): array
{
define('STRING_ONE', '1');
define('INT_ONE', 1);
define('STRING_FOO', 'foo');

return [
[
'0|1|2',
Expand Down Expand Up @@ -2797,12 +2801,6 @@ public function testLiteralArraysKeys(
string $evaluatedPointExpressionType
): void
{
if (!defined('STRING_ONE')) {
define('STRING_ONE', '1');
define('INT_ONE', 1);
define('STRING_FOO', 'foo');
}

$this->assertTypes(
__DIR__ . '/data/literal-arrays-keys.php',
$description,
Expand Down Expand Up @@ -5250,6 +5248,8 @@ public function testCombineTypes(

public function dataConstants(): array
{
define('ConstantsForNodeScopeResolverTest\\FOO_CONSTANT', 1);

return [
[
'1',
Expand All @@ -5259,6 +5259,14 @@ public function dataConstants(): array
'*ERROR*',
'NONEXISTENT_CONSTANT',
],
[
"'bar'",
'\\BAR_CONSTANT',
],
[
'mixed',
'\\BAZ_CONSTANT',
],
];
}

Expand All @@ -5272,9 +5280,6 @@ public function testConstants(
string $expression
): void
{
if (!defined('ConstantsForNodeScopeResolverTest\\FOO_CONSTANT')) {
define('ConstantsForNodeScopeResolverTest\\FOO_CONSTANT', 1);
}
$this->assertTypes(
__DIR__ . '/data/constants.php',
$description,
Expand Down
6 changes: 5 additions & 1 deletion tests/PHPStan/Analyser/data/constants.php
Expand Up @@ -4,4 +4,8 @@

$foo = FOO_CONSTANT;

die;
define('BAR_CONSTANT', 'bar');

if (defined('BAZ_CONSTANT')) {
die;
}
15 changes: 15 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/check-type-function-call.php
Expand Up @@ -159,3 +159,18 @@ public function doFoo(
}

}

class DefinedConstant
{

public function doFoo()
{
if (defined('DEFINITELY_DOES_NOT_EXIST')) {

}
if (!defined('ANOTHER_DEFINITELY_DOES_NOT_EXIST')) {

}
}

}
10 changes: 9 additions & 1 deletion tests/PHPStan/Rules/Constants/ConstantRuleTest.php
Expand Up @@ -7,7 +7,7 @@ class ConstantRuleTest extends \PHPStan\Testing\RuleTestCase

protected function getRule(): \PHPStan\Rules\Rule
{
return new ConstantRule($this->createBroker());
return new ConstantRule();
}

public function testConstants(): void
Expand All @@ -20,6 +20,14 @@ public function testConstants(): void
'Constant NONEXISTENT_CONSTANT not found.',
10,
],
[
'Constant DEFINED_CONSTANT not found.',
13,
],
/*[
'Constant DEFINED_CONSTANT_IF not found.',
21,
],*/
]);
}

Expand Down
12 changes: 12 additions & 0 deletions tests/PHPStan/Rules/Constants/data/constants.php
Expand Up @@ -8,3 +8,15 @@
echo BAR_CONSTANT;
echo BAZ_CONSTANT;
echo NONEXISTENT_CONSTANT;

function () {
echo DEFINED_CONSTANT;
define('DEFINED_CONSTANT', true);
echo DEFINED_CONSTANT;

if (defined('DEFINED_CONSTANT_IF')) {
echo DEFINED_CONSTANT_IF;
}

echo DEFINED_CONSTANT_IF;
};

0 comments on commit a4e248e

Please sign in to comment.