1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Improve function call resolution

This commit is contained in:
Matthew Brown 2019-01-05 14:50:11 -05:00
parent 632f613ba0
commit f5378bdca8
4 changed files with 86 additions and 59 deletions

View File

@ -200,10 +200,39 @@ class FunctionCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expressio
$stmt->inferredType = Type::getMixed(); $stmt->inferredType = Type::getMixed();
} }
} else { } else {
$function_id = implode('\\', $stmt->name->parts); $original_function_id = implode('\\', $stmt->name->parts);
$in_call_map = CallMap::inCallMap($function_id); if (!$stmt->name instanceof PhpParser\Node\Name\FullyQualified) {
$is_stubbed = $codebase_functions->hasStubbedFunction($function_id); $function_id = $codebase_functions->getFullyQualifiedFunctionNameFromString(
$original_function_id,
$statements_analyzer
);
} else {
$function_id = $original_function_id;
}
$namespaced_function_exists = $codebase_functions->functionExists(
$statements_analyzer,
strtolower($function_id)
);
if (!$namespaced_function_exists
&& !$stmt->name instanceof PhpParser\Node\Name\FullyQualified
) {
$in_call_map = CallMap::inCallMap($original_function_id);
$is_stubbed = $codebase_functions->hasStubbedFunction($original_function_id);
if ($is_stubbed || $in_call_map) {
$function_id = $original_function_id;
}
} else {
$in_call_map = CallMap::inCallMap($function_id);
$is_stubbed = $codebase_functions->hasStubbedFunction($function_id);
}
if ($is_stubbed || $in_call_map || $namespaced_function_exists) {
$function_exists = true;
}
$is_predefined = true; $is_predefined = true;
@ -212,17 +241,9 @@ class FunctionCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expressio
if (!$in_call_map) { if (!$in_call_map) {
$predefined_functions = $config->getPredefinedFunctions(); $predefined_functions = $config->getPredefinedFunctions();
$is_predefined = isset($predefined_functions[$function_id]); $is_predefined = isset($predefined_functions[strtolower($original_function_id)])
} || isset($predefined_functions[strtolower($function_id)]);
if (!$in_call_map && !$stmt->name instanceof PhpParser\Node\Name\FullyQualified) {
$function_id = $codebase_functions->getFullyQualifiedFunctionNameFromString(
$function_id,
$statements_analyzer
);
}
if (!$in_call_map) {
if ($context->check_functions) { if ($context->check_functions) {
if (self::checkFunctionExists( if (self::checkFunctionExists(
$statements_analyzer, $statements_analyzer,
@ -233,18 +254,7 @@ class FunctionCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expressio
) { ) {
return false; return false;
} }
} else {
$function_id = self::getExistingFunctionId(
$statements_analyzer,
$function_id,
$is_maybe_root_function
);
} }
$function_exists = $is_stubbed || $codebase_functions->functionExists(
$statements_analyzer,
strtolower($function_id)
);
} else { } else {
$function_exists = true; $function_exists = true;
} }

View File

@ -2099,41 +2099,6 @@ class CallAnalyzer
return true; return true;
} }
/**
* @param StatementsAnalyzer $statements_analyzer
* @param string $function_id
* @param bool $can_be_in_root_scope if true, the function can be shortened to the root version
*
* @return string
*/
protected static function getExistingFunctionId(
StatementsAnalyzer $statements_analyzer,
$function_id,
$can_be_in_root_scope
) {
$function_id = strtolower($function_id);
$codebase = $statements_analyzer->getCodebase();
if ($codebase->functions->functionExists($statements_analyzer, $function_id)) {
return $function_id;
}
if (!$can_be_in_root_scope) {
return $function_id;
}
$root_function_id = preg_replace('/.*\\\/', '', $function_id);
if ($function_id !== $root_function_id
&& $codebase->functions->functionExists($statements_analyzer, $root_function_id)
) {
return $root_function_id;
}
return $function_id;
}
/** /**
* @param PhpParser\Node\Identifier|PhpParser\Node\Name $expr * @param PhpParser\Node\Identifier|PhpParser\Node\Name $expr
* @param \Psalm\Storage\Assertion[] $assertions * @param \Psalm\Storage\Assertion[] $assertions
@ -2159,6 +2124,8 @@ class CallAnalyzer
foreach ($assertions as $assertion) { foreach ($assertions as $assertion) {
$assertion_var_id = null; $assertion_var_id = null;
$arg_value = null;
if (is_int($assertion->var_id)) { if (is_int($assertion->var_id)) {
if (!isset($args[$assertion->var_id])) { if (!isset($args[$assertion->var_id])) {
continue; continue;
@ -2229,6 +2196,23 @@ class CallAnalyzer
} else { } else {
$type_assertions[$assertion_var_id] = $assertion->rule; $type_assertions[$assertion_var_id] = $assertion->rule;
} }
} elseif ($arg_value && $assertion->rule === [['!falsy']]) {
$assert_clauses = \Psalm\Type\Algebra::getFormula(
$arg_value,
$statements_analyzer->getFQCLN(),
$statements_analyzer,
$statements_analyzer->getCodebase()
);
$simplified_clauses = \Psalm\Type\Algebra::simplifyCNF(
array_merge($context->clauses, $assert_clauses)
);
$assert_type_assertions = \Psalm\Type\Algebra::getTruthsFromFormula(
$simplified_clauses
);
$type_assertions = array_merge($type_assertions, $assert_type_assertions);
} }
} }

View File

@ -30,6 +30,23 @@ class AssertTest extends TestCase
$a->foo(); $a->foo();
}', }',
], ],
'dropInReplacementForAssert' => [
'<?php
/**
* @param mixed $_b
* @psalm-assert !falsy $_b
*/
function myAssert($_b) : void {
if (!$_b) {
throw new \Exception("bad");
}
}
function bar(?string $s) : string {
myAssert($s !== null);
return $s;
}'
],
'assertInstanceOfInterface' => [ 'assertInstanceOfInterface' => [
'<?php '<?php
class A { class A {

View File

@ -1268,6 +1268,22 @@ class FunctionCallTest extends TestCase
foo($a); foo($a);
}', }',
], ],
'SKIPPED-getTypeHasValues' => [
'<?php
/**
* @param mixed $maybe
*/
function matchesTypes($maybe) : void {
$t = gettype($maybe);
if ($t === "object") {}
}'
],
'functionResolutionInNamespace' => [
'<?php
namespace Foo;
function sort(int $_) : void {}
sort(5);'
],
]; ];
} }