Skip to content

Commit

Permalink
Use types for method_exists checks in some places
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Aug 19, 2019
1 parent 80949b2 commit 920c2d8
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,17 @@ function (Assertion $assertion) use ($generic_params) : Assertion {
$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_) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,22 +495,29 @@ private static function analyzeAtomicCall(

$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
}
}
}

Expand Down
10 changes: 8 additions & 2 deletions src/Psalm/Internal/Type/AssertionReconciler.php
Original file line number Diff line number Diff line change
Expand Up @@ -820,12 +820,18 @@ private static function reconcileHasMethod(
$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;
Expand Down
4 changes: 2 additions & 2 deletions src/Psalm/Type/Atomic/TObjectWithProperties.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ class TObjectWithProperties extends TObject
public $properties;

/**
* @var array<string, bool>
* @var array<string, string>
*/
public $methods;

/**
* 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 = [])
{
Expand Down
25 changes: 25 additions & 0 deletions tests/MethodCallTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,31 @@ function foo(DateTime $d1, DateTime $d2) : void {
);
}',
],
'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();
})();
}
}'
],
];
}

Expand Down

0 comments on commit 920c2d8

Please sign in to comment.