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:
parent
4fd46fe2c8
commit
9b06b672ba
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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(
|
||||
|
@ -62,4 +62,9 @@ class FunctionLikeStorage
|
||||
* @var int
|
||||
*/
|
||||
public $required_param_count;
|
||||
|
||||
/**
|
||||
* @var array<string, Type\Union>
|
||||
*/
|
||||
public $defined_constants = [];
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user