1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Use types for method_exists checks in some places

This commit is contained in:
Matthew Brown 2019-08-18 22:27:19 -04:00
parent 80949b2b29
commit 920c2d83c3
5 changed files with 68 additions and 20 deletions

View File

@ -700,7 +700,17 @@ class FunctionCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expressio
$first_arg = isset($stmt->args[0]) ? $stmt->args[0] : null;
if ($function->parts === ['method_exists']) {
$context->check_methods = false;
$second_arg = isset($stmt->args[1]) ? $stmt->args[1] : null;
if ($first_arg
&& $first_arg->value instanceof PhpParser\Node\Expr\Variable
&& $second_arg
&& $second_arg->value instanceof PhpParser\Node\Scalar\String_
) {
// do nothing
} else {
$context->check_methods = false;
}
} elseif ($function->parts === ['class_exists']) {
if ($first_arg) {
if ($first_arg->value instanceof PhpParser\Node\Scalar\String_) {

View File

@ -495,22 +495,29 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
$has_mixed_method_call = true;
if ($stmt->name instanceof PhpParser\Node\Identifier) {
$codebase->analyzer->addMixedMemberName(
strtolower($stmt->name->name),
$context->calling_method_id ?: $statements_analyzer->getFileName()
);
}
if ($lhs_type_part instanceof Type\Atomic\TObjectWithProperties
&& $stmt->name instanceof PhpParser\Node\Identifier
&& isset($lhs_type_part->methods[$stmt->name->name])
) {
$existent_method_ids[] = $lhs_type_part->methods[$stmt->name->name];
} else {
if ($stmt->name instanceof PhpParser\Node\Identifier) {
$codebase->analyzer->addMixedMemberName(
strtolower($stmt->name->name),
$context->calling_method_id ?: $statements_analyzer->getFileName()
);
}
if ($context->check_methods) {
if (IssueBuffer::accepts(
new MixedMethodCall(
'Cannot determine the type of the object on the left hand side of this expression',
new CodeLocation($source, $stmt->name)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
if ($context->check_methods) {
if (IssueBuffer::accepts(
new MixedMethodCall(
'Cannot determine the type of the object on the left hand side of this expression',
new CodeLocation($source, $stmt->name)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
}

View File

@ -820,12 +820,18 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
$object_types[] = $type;
if (!$codebase->methodExists($type->value . '::' . $method_id)) {
$obj = new Atomic\TObjectWithProperties([], [$method_id => true]);
$obj = new Atomic\TObjectWithProperties(
[],
[$method_id => $type->value . '::' . $method_id]
);
$type->extra_types[$obj->getKey()] = $obj;
$did_remove_type = true;
}
} elseif ($type instanceof TObject || $type instanceof TMixed) {
$object_types[] = new Atomic\TObjectWithProperties([], [$method_id => true]);
$object_types[] = new Atomic\TObjectWithProperties(
[],
[$method_id => 'object::' . $method_id]
);
$did_remove_type = true;
} elseif ($type instanceof TTemplateParam) {
$object_types[] = $type;

View File

@ -18,7 +18,7 @@ class TObjectWithProperties extends TObject
public $properties;
/**
* @var array<string, bool>
* @var array<string, string>
*/
public $methods;
@ -26,7 +26,7 @@ class TObjectWithProperties extends TObject
* Constructs a new instance of a generic type
*
* @param array<string|int, Union> $properties
* @param array<string, bool> $methods
* @param array<string, string> $methods
*/
public function __construct(array $properties, array $methods = [])
{

View File

@ -341,6 +341,31 @@ class MethodCallTest extends TestCase
);
}',
],
'callMethodAfterCheckingExistence' => [
'<?php
class A {}
function foo(A $a) : void {
if (method_exists($a, "bar")) {
/** @psalm-suppress MixedArgument */
echo $a->bar();
}
}'
],
'callMethodAfterCheckingExistenceInClosure' => [
'<?php
class A {}
function foo(A $a) : void {
if (method_exists($a, "bar")) {
(function() use ($a) : void {
/** @psalm-suppress MixedArgument */
echo $a->bar();
})();
}
}'
],
];
}