1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +01:00

Fix #54 - register function constants when function is invoked

This commit is contained in:
Matthew Brown 2017-01-15 16:43:49 -05:00
parent 4fd46fe2c8
commit 9b06b672ba
6 changed files with 117 additions and 6 deletions

View File

@ -89,6 +89,18 @@ class FunctionChecker extends FunctionLikeChecker
return $file_storage->functions[$function_id]->params;
}
/**
* @param string $function_id
* @param string $file_path
* @return array<string, Type\Union>
*/
public static function getDefinedConstants($function_id, $file_path)
{
$file_storage = FileChecker::$storage[$file_path];
return $file_storage->functions[$function_id]->defined_constants;
}
/**
* @param string $function_id
* @param string $file_path

View File

@ -487,7 +487,8 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
if (!$param->variadic && $has_optional_param) {
if (IssueBuffer::accepts(
new MisplacedRequiredParam(
'Required param $' . $param->name . ' should come before any optional params in ' . $cased_function_id,
'Required param $' . $param->name . ' should come before any optional params in ' .
$cased_function_id,
new CodeLocation($source, $param, true)
),
$source->getSuppressedIssues()
@ -504,6 +505,24 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
$storage->required_param_count = $required_param_count;
if ($function->stmts) {
// look for constant declarations
foreach ($function->stmts as $stmt) {
if ($stmt instanceof PhpParser\Node\Expr\FuncCall &&
$stmt->name instanceof PhpParser\Node\Name &&
$stmt->name->parts === ['define']
) {
$first_arg_value = isset($stmt->args[0]) ? $stmt->args[0]->value : null;
$second_arg_value = isset($stmt->args[1]) ? $stmt->args[1]->value : null;
if ($first_arg_value instanceof PhpParser\Node\Scalar\String_ && $second_arg_value) {
$storage->defined_constants[$first_arg_value->value] =
StatementsChecker::getSimpleType($second_arg_value) ?: Type::getMixed();
}
}
}
}
$config = Config::getInstance();
$docblock_info = null;
@ -1228,10 +1247,6 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
if ($fq_class_name && ClassLikeChecker::isUserDefined($fq_class_name)) {
$method_params = MethodChecker::getMethodParams($method_id);
if ($method_params === null) {
throw new \UnexpectedValueException('Not expecting $method_params to be null');
}
return $method_params;
}

View File

@ -36,7 +36,7 @@ class MethodChecker extends FunctionLikeChecker
/**
* @param string $method_id
* @return array<int, \Psalm\FunctionLikeParameter>|null
* @return array<int, \Psalm\FunctionLikeParameter>
*/
public static function getMethodParams($method_id)
{
@ -47,6 +47,25 @@ class MethodChecker extends FunctionLikeChecker
return $storage->params;
}
}
throw new \UnexpectedValueException('Cannot get method params for ' . $method_id);
}
/**
* @param string $method_id
* @return array<string, Type\Union>
*/
public static function getDefinedConstants($method_id)
{
if ($method_id = self::getDeclaringMethodId($method_id)) {
$storage = self::getStorage($method_id);
if ($storage) {
return $storage->defined_constants;
}
}
throw new \UnexpectedValueException('Cannot get defined constants for ' . $method_id);
}
/**

View File

@ -123,6 +123,8 @@ class CallChecker
$code_location = new CodeLocation($statements_checker->getSource(), $stmt);
$defined_constants = [];
if ($stmt->name instanceof PhpParser\Node\Expr) {
if (ExpressionChecker::analyze($statements_checker, $stmt->name, $context) === false) {
return false;
@ -196,6 +198,13 @@ class CallChecker
$statements_checker->getFilePath(),
$statements_checker->getFileChecker()
);
if (!$in_call_map) {
$defined_constants = FunctionChecker::getDefinedConstants(
$method_id,
$statements_checker->getFilePath()
);
}
}
if (self::checkFunctionArguments(
@ -227,6 +236,11 @@ class CallChecker
// fall through
}
foreach ($defined_constants as $const_name => $const_type) {
$context->constants[$const_name] = clone $const_type;
$context->vars_in_scope[$const_name] = clone $const_type;
}
if ($method_id) {
if ($in_call_map) {
$stmt->inferredType = FunctionChecker::getReturnTypeFromCallMapWithArgs(

View File

@ -62,4 +62,9 @@ class FunctionLikeStorage
* @var int
*/
public $required_param_count;
/**
* @var array<string, Type\Union>
*/
public $defined_constants = [];
}

View File

@ -52,4 +52,50 @@ class ConstantTest extends PHPUnit_Framework_TestCase
$context = new Context('somefile.php');
$file_checker->visitAndAnalyzeMethods($context);
}
/**
* @return void
*/
public function testConstantDefinedInFunction()
{
$stmts = self::$parser->parse('<?php
/**
* @return void
*/
function defineConstant() {
define("CONSTANT", 1);
}
defineConstant();
echo CONSTANT;
');
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
$context = new Context('somefile.php');
$file_checker->visitAndAnalyzeMethods($context);
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage UndefinedConstant
* @return void
*/
public function testConstantDefinedInFunctionButNotCalled()
{
$stmts = self::$parser->parse('<?php
/**
* @return void
*/
function defineConstant() {
define("CONSTANT", 1);
}
echo CONSTANT;
');
$file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts);
$context = new Context('somefile.php');
$file_checker->visitAndAnalyzeMethods($context);
}
}