2018-01-29 00:29:38 +01:00
|
|
|
<?php
|
2018-11-06 03:57:36 +01:00
|
|
|
namespace Psalm\Internal\Analyzer\Statements\Expression\Call;
|
2018-01-29 00:29:38 +01:00
|
|
|
|
|
|
|
use PhpParser;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\MethodAnalyzer;
|
2019-05-14 23:30:16 +02:00
|
|
|
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
2020-06-21 00:05:35 +02:00
|
|
|
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\InstancePropertyFetchAnalyzer;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
2018-01-29 00:29:38 +01:00
|
|
|
use Psalm\CodeLocation;
|
|
|
|
use Psalm\Context;
|
2018-11-12 16:46:55 +01:00
|
|
|
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
2020-06-10 00:39:52 +02:00
|
|
|
use Psalm\Internal\MethodIdentifier;
|
2020-03-11 15:18:40 +01:00
|
|
|
use Psalm\Issue\AbstractMethodCall;
|
2018-01-29 00:29:38 +01:00
|
|
|
use Psalm\Issue\DeprecatedClass;
|
2019-07-18 07:31:48 +02:00
|
|
|
use Psalm\Issue\ImpureMethodCall;
|
2018-03-06 18:19:50 +01:00
|
|
|
use Psalm\Issue\InvalidStringClass;
|
2018-12-02 00:37:49 +01:00
|
|
|
use Psalm\Issue\InternalClass;
|
2019-01-02 15:00:45 +01:00
|
|
|
use Psalm\Issue\MixedMethodCall;
|
2020-03-01 23:25:50 +01:00
|
|
|
use Psalm\Issue\NonStaticSelfCall;
|
2018-01-29 00:29:38 +01:00
|
|
|
use Psalm\Issue\ParentNotFound;
|
2018-03-06 18:19:50 +01:00
|
|
|
use Psalm\Issue\UndefinedClass;
|
2018-07-22 01:56:26 +02:00
|
|
|
use Psalm\Issue\UndefinedMethod;
|
2018-01-29 00:29:38 +01:00
|
|
|
use Psalm\IssueBuffer;
|
2019-01-23 05:42:54 +01:00
|
|
|
use Psalm\Storage\Assertion;
|
2018-01-29 00:29:38 +01:00
|
|
|
use Psalm\Type;
|
|
|
|
use Psalm\Type\Atomic\TNamedObject;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function count;
|
|
|
|
use function in_array;
|
|
|
|
use function strtolower;
|
|
|
|
use function array_map;
|
|
|
|
use function explode;
|
|
|
|
use function strpos;
|
|
|
|
use function is_string;
|
|
|
|
use function strlen;
|
|
|
|
use function substr;
|
2020-06-21 06:58:56 +02:00
|
|
|
use Psalm\Internal\Taint\Source;
|
2020-05-22 04:47:58 +02:00
|
|
|
use Psalm\Internal\Taint\TaintNode;
|
2018-01-29 00:29:38 +01:00
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
class StaticCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer
|
2018-01-29 00:29:38 +01:00
|
|
|
{
|
|
|
|
public static function analyze(
|
2018-11-11 18:01:14 +01:00
|
|
|
StatementsAnalyzer $statements_analyzer,
|
2018-01-29 00:29:38 +01:00
|
|
|
PhpParser\Node\Expr\StaticCall $stmt,
|
|
|
|
Context $context
|
2020-05-18 21:13:27 +02:00
|
|
|
) : bool {
|
2018-01-29 00:29:38 +01:00
|
|
|
$method_id = null;
|
2019-10-19 23:59:10 +02:00
|
|
|
$cased_method_id = null;
|
2018-01-29 00:29:38 +01:00
|
|
|
|
|
|
|
$lhs_type = null;
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$file_analyzer = $statements_analyzer->getFileAnalyzer();
|
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
|
|
|
$source = $statements_analyzer->getSource();
|
2018-01-29 00:29:38 +01:00
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
$stmt_type = null;
|
2018-01-29 00:29:38 +01:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$config = $codebase->config;
|
2018-03-06 17:20:54 +01:00
|
|
|
|
2018-01-29 00:29:38 +01:00
|
|
|
if ($stmt->class instanceof PhpParser\Node\Name) {
|
|
|
|
$fq_class_name = null;
|
|
|
|
|
|
|
|
if (count($stmt->class->parts) === 1
|
|
|
|
&& in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)
|
|
|
|
) {
|
|
|
|
if ($stmt->class->parts[0] === 'parent') {
|
|
|
|
$child_fq_class_name = $context->self;
|
|
|
|
|
|
|
|
$class_storage = $child_fq_class_name
|
2018-11-06 03:57:36 +01:00
|
|
|
? $codebase->classlike_storage_provider->get($child_fq_class_name)
|
2018-01-29 00:29:38 +01:00
|
|
|
: null;
|
|
|
|
|
2019-06-01 06:56:54 +02:00
|
|
|
if (!$class_storage || !$class_storage->parent_class) {
|
2018-01-29 00:29:38 +01:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new ParentNotFound(
|
|
|
|
'Cannot call method on parent as this class does not extend another',
|
2018-11-11 18:01:14 +01:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
2018-01-29 00:29:38 +01:00
|
|
|
),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2018-01-29 00:29:38 +01:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
|
|
|
|
2019-06-01 06:56:54 +02:00
|
|
|
$fq_class_name = $class_storage->parent_class;
|
2018-01-29 00:29:38 +01:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
|
2018-01-29 00:29:38 +01:00
|
|
|
|
|
|
|
$fq_class_name = $class_storage->name;
|
2018-07-18 04:50:30 +02:00
|
|
|
} elseif ($context->self) {
|
|
|
|
if ($stmt->class->parts[0] === 'static' && isset($context->vars_in_scope['$this'])) {
|
|
|
|
$fq_class_name = (string) $context->vars_in_scope['$this'];
|
|
|
|
$lhs_type = clone $context->vars_in_scope['$this'];
|
|
|
|
} else {
|
|
|
|
$fq_class_name = $context->self;
|
|
|
|
}
|
2018-01-29 00:29:38 +01:00
|
|
|
} else {
|
2020-03-01 23:25:50 +01:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new NonStaticSelfCall(
|
|
|
|
'Cannot use ' . $stmt->class->parts[0] . ' outside class context',
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2018-01-29 00:29:38 +01:00
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($context->isPhantomClass($fq_class_name)) {
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
|
|
|
} elseif ($context->check_classes) {
|
2018-11-11 18:01:14 +01:00
|
|
|
$aliases = $statements_analyzer->getAliases();
|
2018-10-27 05:04:38 +02:00
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
if ($context->calling_method_id
|
2018-10-27 05:04:38 +02:00
|
|
|
&& !$stmt->class instanceof PhpParser\Node\Name\FullyQualified
|
|
|
|
) {
|
2019-04-16 22:07:48 +02:00
|
|
|
$codebase->file_reference_provider->addMethodReferenceToClassMember(
|
2020-03-26 17:35:27 +01:00
|
|
|
$context->calling_method_id,
|
2018-11-11 18:01:14 +01:00
|
|
|
'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath())
|
2018-10-27 05:04:38 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
2018-01-29 00:29:38 +01:00
|
|
|
$stmt->class,
|
2018-10-27 05:04:38 +02:00
|
|
|
$aliases
|
2018-01-29 00:29:38 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
if ($context->isPhantomClass($fq_class_name)) {
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$does_class_exist = false;
|
|
|
|
|
|
|
|
if ($context->self) {
|
2018-11-06 03:57:36 +01:00
|
|
|
$self_storage = $codebase->classlike_storage_provider->get($context->self);
|
2018-01-29 00:29:38 +01:00
|
|
|
|
|
|
|
if (isset($self_storage->used_traits[strtolower($fq_class_name)])) {
|
|
|
|
$fq_class_name = $context->self;
|
|
|
|
$does_class_exist = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-19 01:46:05 +01:00
|
|
|
if (!isset($context->phantom_classes[strtolower($fq_class_name)])
|
|
|
|
&& !$does_class_exist
|
|
|
|
) {
|
2018-11-06 03:57:36 +01:00
|
|
|
$does_class_exist = ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2018-01-29 00:29:38 +01:00
|
|
|
$fq_class_name,
|
|
|
|
new CodeLocation($source, $stmt->class),
|
2020-03-28 19:45:58 +01:00
|
|
|
!$context->collect_initializations
|
|
|
|
&& !$context->collect_mutations
|
|
|
|
? $context->self
|
|
|
|
: null,
|
|
|
|
!$context->collect_initializations
|
|
|
|
&& !$context->collect_mutations
|
|
|
|
? $context->calling_method_id
|
|
|
|
: null,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
2019-03-28 13:22:44 +01:00
|
|
|
false,
|
|
|
|
false,
|
2018-01-29 00:29:38 +01:00
|
|
|
false
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$does_class_exist) {
|
2020-05-18 21:13:27 +02:00
|
|
|
return $does_class_exist !== false;
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-01 15:55:39 +02:00
|
|
|
if ($codebase->store_node_types
|
|
|
|
&& $fq_class_name
|
|
|
|
&& !$context->collect_initializations
|
|
|
|
&& !$context->collect_mutations
|
|
|
|
) {
|
2018-10-26 22:17:15 +02:00
|
|
|
$codebase->analyzer->addNodeReference(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getFilePath(),
|
2018-10-26 22:17:15 +02:00
|
|
|
$stmt->class,
|
|
|
|
$fq_class_name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-07-18 04:50:30 +02:00
|
|
|
if ($fq_class_name && !$lhs_type) {
|
2018-01-29 00:29:38 +01:00
|
|
|
$lhs_type = new Type\Union([new TNamedObject($fq_class_name)]);
|
|
|
|
}
|
|
|
|
} else {
|
2018-11-11 18:01:14 +01:00
|
|
|
ExpressionAnalyzer::analyze($statements_analyzer, $stmt->class, $context);
|
2019-11-25 17:44:54 +01:00
|
|
|
$lhs_type = $statements_analyzer->node_data->getType($stmt->class) ?: Type::getMixed();
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
|
|
|
|
2019-01-23 21:50:12 +01:00
|
|
|
if (!$lhs_type) {
|
2020-05-19 04:57:00 +02:00
|
|
|
if (ArgumentsAnalyzer::analyze(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2018-06-26 01:38:15 +02:00
|
|
|
$stmt->args,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
$context
|
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$has_mock = false;
|
2019-06-14 20:41:36 +02:00
|
|
|
$moved_call = false;
|
2018-01-29 00:29:38 +01:00
|
|
|
|
2020-01-04 18:20:26 +01:00
|
|
|
foreach ($lhs_type->getAtomicTypes() as $lhs_type_part) {
|
2019-01-20 05:25:23 +01:00
|
|
|
$intersection_types = [];
|
|
|
|
|
2019-01-02 15:00:45 +01:00
|
|
|
if ($lhs_type_part instanceof TNamedObject) {
|
|
|
|
$fq_class_name = $lhs_type_part->value;
|
2019-01-20 05:25:23 +01:00
|
|
|
|
2019-01-28 07:12:41 +01:00
|
|
|
if (!ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
|
|
|
$statements_analyzer,
|
|
|
|
$fq_class_name,
|
|
|
|
new CodeLocation($source, $stmt->class),
|
2020-03-28 19:45:58 +01:00
|
|
|
!$context->collect_initializations
|
|
|
|
&& !$context->collect_mutations
|
|
|
|
? $context->self
|
|
|
|
: null,
|
|
|
|
!$context->collect_initializations
|
|
|
|
&& !$context->collect_mutations
|
|
|
|
? $context->calling_method_id
|
|
|
|
: null,
|
2019-01-28 07:12:41 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
2019-12-16 17:46:10 +01:00
|
|
|
$stmt->class instanceof PhpParser\Node\Name
|
2020-01-01 18:49:12 +01:00
|
|
|
&& count($stmt->class->parts) === 1
|
2019-12-16 17:46:10 +01:00
|
|
|
&& in_array(strtolower($stmt->class->parts[0]), ['self', 'static'], true)
|
2019-01-28 07:12:41 +01:00
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-20 05:25:23 +01:00
|
|
|
$intersection_types = $lhs_type_part->extra_types;
|
2019-01-02 15:00:45 +01:00
|
|
|
} elseif ($lhs_type_part instanceof Type\Atomic\TClassString
|
2019-01-20 04:45:58 +01:00
|
|
|
&& $lhs_type_part->as_type
|
2019-01-02 15:00:45 +01:00
|
|
|
) {
|
2019-01-20 04:45:58 +01:00
|
|
|
$fq_class_name = $lhs_type_part->as_type->value;
|
2019-01-20 05:25:23 +01:00
|
|
|
|
2019-01-28 07:12:41 +01:00
|
|
|
if (!ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
|
|
|
$statements_analyzer,
|
|
|
|
$fq_class_name,
|
|
|
|
new CodeLocation($source, $stmt->class),
|
2020-02-11 22:39:33 +01:00
|
|
|
$context->self,
|
2020-03-26 17:35:27 +01:00
|
|
|
$context->calling_method_id,
|
2019-01-28 07:12:41 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
|
|
|
false
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-20 05:25:23 +01:00
|
|
|
$intersection_types = $lhs_type_part->as_type->extra_types;
|
2019-12-07 20:03:20 +01:00
|
|
|
} elseif ($lhs_type_part instanceof Type\Atomic\GetClassT
|
|
|
|
&& !$lhs_type_part->as_type->hasObject()
|
|
|
|
) {
|
|
|
|
$fq_class_name = 'object';
|
|
|
|
|
2019-12-07 20:05:37 +01:00
|
|
|
if ($lhs_type_part->as_type->hasObjectType()
|
2019-12-07 20:03:20 +01:00
|
|
|
&& $lhs_type_part->as_type->isSingle()
|
|
|
|
) {
|
2020-01-04 18:20:26 +01:00
|
|
|
foreach ($lhs_type_part->as_type->getAtomicTypes() as $typeof_type_atomic) {
|
2019-12-07 20:03:20 +01:00
|
|
|
if ($typeof_type_atomic instanceof Type\Atomic\TNamedObject) {
|
|
|
|
$fq_class_name = $typeof_type_atomic->value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-02-27 14:56:39 +01:00
|
|
|
|
|
|
|
if ($fq_class_name === 'object') {
|
|
|
|
continue;
|
|
|
|
}
|
2019-01-02 15:00:45 +01:00
|
|
|
} elseif ($lhs_type_part instanceof Type\Atomic\TLiteralClassString) {
|
|
|
|
$fq_class_name = $lhs_type_part->value;
|
2019-01-28 07:12:41 +01:00
|
|
|
|
|
|
|
if (!ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
|
|
|
$statements_analyzer,
|
|
|
|
$fq_class_name,
|
|
|
|
new CodeLocation($source, $stmt->class),
|
2020-02-11 22:39:33 +01:00
|
|
|
$context->self,
|
2020-03-26 17:35:27 +01:00
|
|
|
$context->calling_method_id,
|
2019-01-28 07:12:41 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
|
|
|
false
|
|
|
|
)) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-02-22 03:40:06 +01:00
|
|
|
} elseif ($lhs_type_part instanceof Type\Atomic\TTemplateParam
|
2019-01-05 06:15:53 +01:00
|
|
|
&& !$lhs_type_part->as->isMixed()
|
|
|
|
&& !$lhs_type_part->as->hasObject()
|
2019-01-02 15:00:45 +01:00
|
|
|
) {
|
2019-01-05 06:15:53 +01:00
|
|
|
$fq_class_name = null;
|
|
|
|
|
2020-01-04 18:20:26 +01:00
|
|
|
foreach ($lhs_type_part->as->getAtomicTypes() as $generic_param_type) {
|
2019-01-05 06:15:53 +01:00
|
|
|
if (!$generic_param_type instanceof TNamedObject) {
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
$fq_class_name = $generic_param_type->value;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$fq_class_name) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new UndefinedClass(
|
|
|
|
'Type ' . $lhs_type_part->as . ' cannot be called as a class',
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
|
|
|
(string) $lhs_type_part
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
2019-01-02 15:00:45 +01:00
|
|
|
} else {
|
|
|
|
if ($lhs_type_part instanceof Type\Atomic\TMixed
|
2019-02-22 03:40:06 +01:00
|
|
|
|| $lhs_type_part instanceof Type\Atomic\TTemplateParam
|
2019-01-02 15:00:45 +01:00
|
|
|
|| $lhs_type_part instanceof Type\Atomic\TClassString
|
|
|
|
) {
|
2019-04-17 17:12:18 +02:00
|
|
|
if ($stmt->name instanceof PhpParser\Node\Identifier) {
|
2019-04-27 23:38:24 +02:00
|
|
|
$codebase->analyzer->addMixedMemberName(
|
|
|
|
strtolower($stmt->name->name),
|
2020-03-26 17:35:27 +01:00
|
|
|
$context->calling_method_id ?: $statements_analyzer->getFileName()
|
2019-04-27 23:38:24 +02:00
|
|
|
);
|
2019-04-17 17:12:18 +02:00
|
|
|
}
|
|
|
|
|
2019-01-02 15:00:45 +01:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new MixedMethodCall(
|
2019-01-20 04:45:58 +01:00
|
|
|
'Cannot call method on an unknown class',
|
2019-01-02 15:00:45 +01:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
2018-03-06 17:20:54 +01:00
|
|
|
|
2018-05-21 18:40:39 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-03-06 17:20:54 +01:00
|
|
|
if ($lhs_type_part instanceof Type\Atomic\TString) {
|
|
|
|
if ($config->allow_string_standin_for_class
|
|
|
|
&& !$lhs_type_part instanceof Type\Atomic\TNumericString
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
2018-03-06 18:19:50 +01:00
|
|
|
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new InvalidStringClass(
|
|
|
|
'String cannot be used as a class',
|
2018-11-11 18:01:14 +01:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
2018-03-06 18:19:50 +01:00
|
|
|
),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2018-03-06 18:19:50 +01:00
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-03-06 20:11:32 +01:00
|
|
|
if ($lhs_type_part instanceof Type\Atomic\TNull
|
|
|
|
&& $lhs_type->ignore_nullable_issues
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-03-06 17:20:54 +01:00
|
|
|
if (IssueBuffer::accepts(
|
2018-03-06 18:19:50 +01:00
|
|
|
new UndefinedClass(
|
2018-03-06 17:20:54 +01:00
|
|
|
'Type ' . $lhs_type_part . ' cannot be called as a class',
|
2018-11-11 18:01:14 +01:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
2018-03-21 15:17:57 +01:00
|
|
|
(string) $lhs_type_part
|
2018-03-06 17:20:54 +01:00
|
|
|
),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2018-03-06 17:20:54 +01:00
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
|
2018-01-29 00:29:38 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-12-21 17:39:28 +01:00
|
|
|
$fq_class_name = $codebase->classlikes->getUnAliasedName($fq_class_name);
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$is_mock = ExpressionAnalyzer::isMock($fq_class_name);
|
2018-01-29 00:29:38 +01:00
|
|
|
|
|
|
|
$has_mock = $has_mock || $is_mock;
|
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
if ($stmt->name instanceof PhpParser\Node\Identifier && !$is_mock) {
|
2019-01-18 17:37:52 +01:00
|
|
|
$method_name_lc = strtolower($stmt->name->name);
|
2020-06-10 00:39:52 +02:00
|
|
|
$method_id = new MethodIdentifier($fq_class_name, $method_name_lc);
|
2020-06-22 00:07:39 +02:00
|
|
|
|
2019-10-19 23:59:10 +02:00
|
|
|
$cased_method_id = $fq_class_name . '::' . $stmt->name->name;
|
2018-01-29 00:29:38 +01:00
|
|
|
|
2019-10-20 05:03:59 +02:00
|
|
|
if ($codebase->store_node_types
|
|
|
|
&& !$context->collect_initializations
|
2019-07-01 21:54:33 +02:00
|
|
|
&& !$context->collect_mutations
|
|
|
|
) {
|
|
|
|
ArgumentMapPopulator::recordArgumentPositions(
|
|
|
|
$statements_analyzer,
|
|
|
|
$stmt,
|
|
|
|
$codebase,
|
2020-02-15 02:54:26 +01:00
|
|
|
(string) $method_id
|
2019-07-01 21:54:33 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
$args = $stmt->args;
|
|
|
|
|
2019-01-20 05:25:23 +01:00
|
|
|
if ($intersection_types
|
|
|
|
&& !$codebase->methods->methodExists($method_id)
|
|
|
|
) {
|
|
|
|
foreach ($intersection_types as $intersection_type) {
|
|
|
|
if (!$intersection_type instanceof TNamedObject) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-06-10 00:39:52 +02:00
|
|
|
$intersection_method_id = new MethodIdentifier(
|
2020-02-15 02:54:26 +01:00
|
|
|
$intersection_type->value,
|
|
|
|
$method_name_lc
|
|
|
|
);
|
2019-01-20 05:25:23 +01:00
|
|
|
|
|
|
|
if ($codebase->methods->methodExists($intersection_method_id)) {
|
|
|
|
$method_id = $intersection_method_id;
|
2019-10-19 23:59:10 +02:00
|
|
|
$cased_method_id = $intersection_type->value . '::' . $stmt->name->name;
|
2019-01-20 05:25:23 +01:00
|
|
|
$fq_class_name = $intersection_type->value;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-02 00:21:32 +01:00
|
|
|
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
|
|
|
|
|
2020-06-10 00:39:52 +02:00
|
|
|
$naive_method_exists = $codebase->methods->methodExists(
|
2019-04-16 22:07:48 +02:00
|
|
|
$method_id,
|
2020-03-28 19:45:58 +01:00
|
|
|
!$context->collect_initializations
|
|
|
|
&& !$context->collect_mutations
|
|
|
|
? $context->calling_method_id
|
|
|
|
: null,
|
2020-03-28 21:30:56 +01:00
|
|
|
$codebase->collect_locations
|
2020-03-28 19:45:58 +01:00
|
|
|
? new CodeLocation($source, $stmt->name)
|
|
|
|
: null,
|
2020-03-06 19:02:34 +01:00
|
|
|
$statements_analyzer,
|
2019-04-16 22:07:48 +02:00
|
|
|
$statements_analyzer->getFilePath()
|
2020-06-10 00:39:52 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
if (!$naive_method_exists
|
|
|
|
&& $class_storage->mixin_declaring_fqcln
|
|
|
|
&& $class_storage->mixin instanceof Type\Atomic\TNamedObject
|
|
|
|
) {
|
|
|
|
$new_method_id = new MethodIdentifier(
|
|
|
|
$class_storage->mixin->value,
|
|
|
|
$method_name_lc
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($codebase->methods->methodExists(
|
|
|
|
$new_method_id,
|
|
|
|
$context->calling_method_id,
|
|
|
|
$codebase->collect_locations
|
|
|
|
? new CodeLocation($source, $stmt->name)
|
|
|
|
: null,
|
|
|
|
!$context->collect_initializations
|
|
|
|
&& !$context->collect_mutations
|
|
|
|
? $statements_analyzer
|
|
|
|
: null,
|
|
|
|
$statements_analyzer->getFilePath()
|
|
|
|
)) {
|
2020-06-21 00:05:35 +02:00
|
|
|
$mixin_candidate_type = new Type\Union([clone $class_storage->mixin]);
|
|
|
|
|
|
|
|
if ($class_storage->mixin instanceof Type\Atomic\TGenericObject) {
|
|
|
|
$mixin_declaring_class_storage = $codebase->classlike_storage_provider->get(
|
|
|
|
$class_storage->mixin_declaring_fqcln
|
|
|
|
);
|
2020-06-10 00:39:52 +02:00
|
|
|
|
2020-06-21 00:05:35 +02:00
|
|
|
$mixin_candidate_type = InstancePropertyFetchAnalyzer::localizePropertyType(
|
|
|
|
$codebase,
|
|
|
|
new Type\Union([$lhs_type_part]),
|
|
|
|
$class_storage->mixin,
|
|
|
|
$class_storage,
|
|
|
|
$mixin_declaring_class_storage
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$new_lhs_type = \Psalm\Internal\Type\TypeExpander::expandUnion(
|
2020-06-10 00:39:52 +02:00
|
|
|
$codebase,
|
2020-06-21 00:05:35 +02:00
|
|
|
$mixin_candidate_type,
|
|
|
|
$fq_class_name,
|
2020-06-10 00:39:52 +02:00
|
|
|
$fq_class_name,
|
|
|
|
$class_storage->parent_class,
|
|
|
|
true,
|
|
|
|
false,
|
|
|
|
$class_storage->final
|
|
|
|
);
|
|
|
|
|
2020-06-21 00:05:35 +02:00
|
|
|
$old_data_provider = $statements_analyzer->node_data;
|
|
|
|
|
|
|
|
$statements_analyzer->node_data = clone $statements_analyzer->node_data;
|
2020-06-10 00:39:52 +02:00
|
|
|
|
2020-06-21 00:05:35 +02:00
|
|
|
$context->vars_in_scope['$tmp_mixin_var'] = $new_lhs_type;
|
|
|
|
|
|
|
|
$fake_method_call_expr = new PhpParser\Node\Expr\MethodCall(
|
|
|
|
new PhpParser\Node\Expr\Variable(
|
|
|
|
'tmp_mixin_var',
|
|
|
|
$stmt->class->getAttributes()
|
|
|
|
),
|
|
|
|
$stmt->name,
|
|
|
|
$stmt->args,
|
|
|
|
$stmt->getAttributes()
|
|
|
|
);
|
|
|
|
|
|
|
|
if (MethodCallAnalyzer::analyze(
|
|
|
|
$statements_analyzer,
|
|
|
|
$fake_method_call_expr,
|
|
|
|
$context
|
|
|
|
) === false) {
|
|
|
|
return false;
|
2020-06-10 00:39:52 +02:00
|
|
|
}
|
|
|
|
|
2020-06-21 00:05:35 +02:00
|
|
|
$fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr);
|
|
|
|
|
|
|
|
$statements_analyzer->node_data = $old_data_provider;
|
|
|
|
|
|
|
|
$statements_analyzer->node_data->setType($stmt, $fake_method_call_type ?: Type::getMixed());
|
2020-06-10 00:39:52 +02:00
|
|
|
|
2020-06-21 00:05:35 +02:00
|
|
|
return true;
|
2020-06-10 00:39:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$naive_method_exists
|
2018-11-06 03:57:36 +01:00
|
|
|
|| !MethodAnalyzer::isMethodVisible(
|
2018-09-26 00:37:24 +02:00
|
|
|
$method_id,
|
2019-03-01 14:57:10 +01:00
|
|
|
$context,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSource()
|
2018-09-26 00:37:24 +02:00
|
|
|
)
|
2020-02-02 00:21:32 +01:00
|
|
|
|| (isset($class_storage->pseudo_static_methods[$method_name_lc])
|
|
|
|
&& ($config->use_phpdoc_method_without_magic_or_parent || $class_storage->parent_class))
|
2018-09-26 00:37:24 +02:00
|
|
|
) {
|
2020-06-10 00:39:52 +02:00
|
|
|
$callstatic_id = new MethodIdentifier(
|
2020-02-15 02:54:26 +01:00
|
|
|
$fq_class_name,
|
|
|
|
'__callstatic'
|
|
|
|
);
|
2018-09-26 00:37:24 +02:00
|
|
|
if ($codebase->methods->methodExists(
|
2020-02-15 02:54:26 +01:00
|
|
|
$callstatic_id,
|
2020-03-26 17:35:27 +01:00
|
|
|
$context->calling_method_id,
|
2020-03-28 21:30:56 +01:00
|
|
|
$codebase->collect_locations
|
|
|
|
? new CodeLocation($source, $stmt->name)
|
|
|
|
: null,
|
2020-03-06 19:48:59 +01:00
|
|
|
!$context->collect_initializations
|
|
|
|
&& !$context->collect_mutations
|
|
|
|
? $statements_analyzer
|
|
|
|
: null,
|
2019-04-16 22:07:48 +02:00
|
|
|
$statements_analyzer->getFilePath()
|
2018-09-26 00:37:24 +02:00
|
|
|
)) {
|
2019-01-18 17:37:52 +01:00
|
|
|
if (isset($class_storage->pseudo_static_methods[$method_name_lc])) {
|
|
|
|
$pseudo_method_storage = $class_storage->pseudo_static_methods[$method_name_lc];
|
|
|
|
|
2020-02-02 00:21:32 +01:00
|
|
|
if (self::checkPseudoMethod(
|
2019-01-18 17:37:52 +01:00
|
|
|
$statements_analyzer,
|
2020-02-02 00:21:32 +01:00
|
|
|
$stmt,
|
2019-01-18 17:37:52 +01:00
|
|
|
$method_id,
|
2020-02-02 00:21:32 +01:00
|
|
|
$fq_class_name,
|
2019-01-18 17:37:52 +01:00
|
|
|
$args,
|
2020-02-02 00:21:32 +01:00
|
|
|
$class_storage,
|
2019-01-18 17:37:52 +01:00
|
|
|
$pseudo_method_storage,
|
|
|
|
$context
|
2020-02-02 00:21:32 +01:00
|
|
|
) === false
|
|
|
|
) {
|
2019-01-18 17:37:52 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($pseudo_method_storage->return_type) {
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
2019-01-18 17:37:52 +01:00
|
|
|
}
|
|
|
|
} else {
|
2020-05-19 04:57:00 +02:00
|
|
|
if (ArgumentsAnalyzer::analyze(
|
2019-01-18 17:37:52 +01:00
|
|
|
$statements_analyzer,
|
|
|
|
$args,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
$context
|
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
$array_values = array_map(
|
|
|
|
/**
|
|
|
|
* @return PhpParser\Node\Expr\ArrayItem
|
|
|
|
*/
|
|
|
|
function (PhpParser\Node\Arg $arg) {
|
|
|
|
return new PhpParser\Node\Expr\ArrayItem($arg->value);
|
|
|
|
},
|
|
|
|
$args
|
|
|
|
);
|
|
|
|
|
|
|
|
$args = [
|
2020-02-15 02:54:26 +01:00
|
|
|
new PhpParser\Node\Arg(new PhpParser\Node\Scalar\String_((string) $method_id)),
|
2018-09-26 00:37:24 +02:00
|
|
|
new PhpParser\Node\Arg(new PhpParser\Node\Expr\Array_($array_values)),
|
|
|
|
];
|
|
|
|
|
2020-06-10 00:39:52 +02:00
|
|
|
$method_id = new MethodIdentifier(
|
2020-02-15 02:54:26 +01:00
|
|
|
$fq_class_name,
|
|
|
|
'__callstatic'
|
|
|
|
);
|
2020-02-02 00:21:32 +01:00
|
|
|
} elseif (isset($class_storage->pseudo_static_methods[$method_name_lc])
|
|
|
|
&& ($config->use_phpdoc_method_without_magic_or_parent || $class_storage->parent_class)
|
|
|
|
) {
|
|
|
|
$pseudo_method_storage = $class_storage->pseudo_static_methods[$method_name_lc];
|
|
|
|
|
|
|
|
if (self::checkPseudoMethod(
|
|
|
|
$statements_analyzer,
|
|
|
|
$stmt,
|
|
|
|
$method_id,
|
|
|
|
$fq_class_name,
|
|
|
|
$args,
|
|
|
|
$class_storage,
|
|
|
|
$pseudo_method_storage,
|
|
|
|
$context
|
|
|
|
) === false
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($pseudo_method_storage->return_type) {
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
2020-02-02 00:21:32 +01:00
|
|
|
}
|
2018-09-26 00:37:24 +02:00
|
|
|
}
|
2019-01-23 21:50:12 +01:00
|
|
|
|
|
|
|
if (!$context->check_methods) {
|
2020-05-19 04:57:00 +02:00
|
|
|
if (ArgumentsAnalyzer::analyze(
|
2019-01-23 21:50:12 +01:00
|
|
|
$statements_analyzer,
|
|
|
|
$stmt->args,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
$context
|
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
2019-01-23 21:50:12 +01:00
|
|
|
}
|
2018-09-26 00:37:24 +02:00
|
|
|
}
|
2018-06-19 22:14:51 +02:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$does_method_exist = MethodAnalyzer::checkMethodExists(
|
|
|
|
$codebase,
|
2019-01-20 05:25:23 +01:00
|
|
|
$method_id,
|
2018-01-29 00:29:38 +01:00
|
|
|
new CodeLocation($source, $stmt),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
2020-03-26 17:35:27 +01:00
|
|
|
$context->calling_method_id
|
2018-01-29 00:29:38 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
if (!$does_method_exist) {
|
2020-05-19 04:57:00 +02:00
|
|
|
if (ArgumentsAnalyzer::analyze(
|
2019-03-01 05:59:17 +01:00
|
|
|
$statements_analyzer,
|
|
|
|
$stmt->args,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
$context
|
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-06-14 20:41:36 +02:00
|
|
|
if ($codebase->alter_code && $fq_class_name && !$moved_call) {
|
|
|
|
$codebase->classlikes->handleClassLikeReferenceInMigration(
|
|
|
|
$codebase,
|
|
|
|
$statements_analyzer,
|
|
|
|
$stmt->class,
|
|
|
|
$fq_class_name,
|
2020-03-26 17:35:27 +01:00
|
|
|
$context->calling_method_id
|
2019-06-14 20:41:36 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
|
2018-01-29 00:29:38 +01:00
|
|
|
|
2019-02-10 21:01:10 +01:00
|
|
|
if ($class_storage->user_defined
|
|
|
|
&& $context->self
|
|
|
|
&& ($context->collect_mutations || $context->collect_initializations)
|
|
|
|
) {
|
2020-02-15 02:54:26 +01:00
|
|
|
$appearing_method_id = $codebase->methods->getAppearingMethodId($method_id);
|
2019-02-10 21:01:10 +01:00
|
|
|
|
|
|
|
if (!$appearing_method_id) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new UndefinedMethod(
|
|
|
|
'Method ' . $method_id . ' does not exist',
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
2020-02-15 02:54:26 +01:00
|
|
|
(string) $method_id
|
2019-02-10 21:01:10 +01:00
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
)) {
|
2019-03-01 14:57:10 +01:00
|
|
|
//
|
2019-02-10 21:01:10 +01:00
|
|
|
}
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
2019-02-10 21:01:10 +01:00
|
|
|
}
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
$appearing_method_class_name = $appearing_method_id->fq_class_name;
|
2019-02-10 21:01:10 +01:00
|
|
|
|
|
|
|
if ($codebase->classExtends($context->self, $appearing_method_class_name)) {
|
|
|
|
$old_context_include_location = $context->include_location;
|
|
|
|
$old_self = $context->self;
|
|
|
|
$context->include_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
|
|
|
|
$context->self = $appearing_method_class_name;
|
|
|
|
|
|
|
|
if ($context->collect_mutations) {
|
|
|
|
$file_analyzer->getMethodMutations($method_id, $context);
|
|
|
|
} else {
|
|
|
|
// collecting initializations
|
|
|
|
$local_vars_in_scope = [];
|
|
|
|
$local_vars_possibly_in_scope = [];
|
|
|
|
|
|
|
|
foreach ($context->vars_in_scope as $var => $_) {
|
|
|
|
if (strpos($var, '$this->') !== 0 && $var !== '$this') {
|
|
|
|
$local_vars_in_scope[$var] = $context->vars_in_scope[$var];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($context->vars_possibly_in_scope as $var => $_) {
|
|
|
|
if (strpos($var, '$this->') !== 0 && $var !== '$this') {
|
|
|
|
$local_vars_possibly_in_scope[$var] = $context->vars_possibly_in_scope[$var];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
if (!isset($context->initialized_methods[(string) $method_id])) {
|
2019-02-10 21:01:10 +01:00
|
|
|
if ($context->initialized_methods === null) {
|
|
|
|
$context->initialized_methods = [];
|
|
|
|
}
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
$context->initialized_methods[(string) $method_id] = true;
|
2019-02-10 21:01:10 +01:00
|
|
|
|
|
|
|
$file_analyzer->getMethodMutations($method_id, $context);
|
|
|
|
|
|
|
|
foreach ($local_vars_in_scope as $var => $type) {
|
|
|
|
$context->vars_in_scope[$var] = $type;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($local_vars_possibly_in_scope as $var => $type) {
|
|
|
|
$context->vars_possibly_in_scope[$var] = $type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$context->include_location = $old_context_include_location;
|
|
|
|
$context->self = $old_self;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-27 19:36:39 +02:00
|
|
|
if ($class_storage->deprecated && $fq_class_name !== $context->self) {
|
2018-01-29 00:29:38 +01:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new DeprecatedClass(
|
|
|
|
$fq_class_name . ' is marked deprecated',
|
2019-06-06 20:48:33 +02:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
|
|
|
$fq_class_name
|
2018-01-29 00:29:38 +01:00
|
|
|
),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2018-01-29 00:29:38 +01:00
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-11 21:39:53 +02:00
|
|
|
if ($class_storage->psalm_internal
|
2019-05-11 15:58:53 +02:00
|
|
|
&& $context->self
|
2019-05-14 23:30:16 +02:00
|
|
|
&& ! NamespaceAnalyzer::isWithin($context->self, $class_storage->psalm_internal)
|
2019-05-11 18:26:42 +02:00
|
|
|
) {
|
2019-05-11 15:58:53 +02:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new InternalClass(
|
2019-05-11 21:39:53 +02:00
|
|
|
$fq_class_name . ' is marked internal to ' . $class_storage->psalm_internal,
|
2019-06-06 20:48:33 +02:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
|
|
|
$fq_class_name
|
2019-05-11 15:58:53 +02:00
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 04:57:00 +02:00
|
|
|
if (Method\MethodVisibilityAnalyzer::analyze(
|
2018-01-29 00:29:38 +01:00
|
|
|
$method_id,
|
2019-03-01 14:57:10 +01:00
|
|
|
$context,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSource(),
|
2018-01-29 00:29:38 +01:00
|
|
|
new CodeLocation($source, $stmt),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2018-01-29 00:29:38 +01:00
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-12-17 23:48:13 +01:00
|
|
|
if ((!$stmt->class instanceof PhpParser\Node\Name
|
|
|
|
|| $stmt->class->parts[0] !== 'parent'
|
|
|
|
|| $statements_analyzer->isStatic())
|
2018-01-29 00:29:38 +01:00
|
|
|
&& (
|
|
|
|
!$context->self
|
2018-11-11 18:01:14 +01:00
|
|
|
|| $statements_analyzer->isStatic()
|
2018-02-01 06:50:01 +01:00
|
|
|
|| !$codebase->classExtends($context->self, $fq_class_name)
|
2018-01-29 00:29:38 +01:00
|
|
|
)
|
|
|
|
) {
|
2018-11-06 03:57:36 +01:00
|
|
|
if (MethodAnalyzer::checkStatic(
|
2018-01-29 00:29:38 +01:00
|
|
|
$method_id,
|
2018-12-17 23:48:13 +01:00
|
|
|
($stmt->class instanceof PhpParser\Node\Name
|
|
|
|
&& strtolower($stmt->class->parts[0]) === 'self')
|
|
|
|
|| $context->self === $fq_class_name,
|
2018-11-11 18:01:14 +01:00
|
|
|
!$statements_analyzer->isStatic(),
|
2018-11-06 03:57:36 +01:00
|
|
|
$codebase,
|
2018-01-29 00:29:38 +01:00
|
|
|
new CodeLocation($source, $stmt),
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
2018-06-04 01:11:07 +02:00
|
|
|
$is_dynamic_this_method
|
2018-01-29 00:29:38 +01:00
|
|
|
) === false) {
|
|
|
|
// fall through
|
|
|
|
}
|
2018-06-04 01:11:07 +02:00
|
|
|
|
|
|
|
if ($is_dynamic_this_method) {
|
2019-11-25 17:44:54 +01:00
|
|
|
$old_data_provider = $statements_analyzer->node_data;
|
|
|
|
|
|
|
|
$statements_analyzer->node_data = clone $statements_analyzer->node_data;
|
|
|
|
|
2018-06-04 01:11:07 +02:00
|
|
|
$fake_method_call_expr = new PhpParser\Node\Expr\MethodCall(
|
|
|
|
new PhpParser\Node\Expr\Variable(
|
|
|
|
'this',
|
|
|
|
$stmt->class->getAttributes()
|
|
|
|
),
|
|
|
|
$stmt->name,
|
|
|
|
$stmt->args,
|
|
|
|
$stmt->getAttributes()
|
|
|
|
);
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
if (MethodCallAnalyzer::analyze(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2018-06-04 01:11:07 +02:00
|
|
|
$fake_method_call_expr,
|
|
|
|
$context
|
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
$fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr);
|
|
|
|
|
|
|
|
$statements_analyzer->node_data = $old_data_provider;
|
|
|
|
|
|
|
|
if ($fake_method_call_type) {
|
|
|
|
$statements_analyzer->node_data->setType($stmt, $fake_method_call_type);
|
2018-06-04 01:11:07 +02:00
|
|
|
}
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
2018-06-04 01:11:07 +02:00
|
|
|
}
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
|
|
|
|
2020-05-19 04:57:00 +02:00
|
|
|
if (Method\MethodCallProhibitionAnalyzer::analyze(
|
2018-11-06 03:57:36 +01:00
|
|
|
$codebase,
|
2018-12-02 00:37:49 +01:00
|
|
|
$context,
|
2018-01-29 00:29:38 +01:00
|
|
|
$method_id,
|
2018-11-11 18:01:14 +01:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2018-01-29 00:29:38 +01:00
|
|
|
) === false) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
|
2020-03-12 04:15:15 +01:00
|
|
|
$found_generic_params = ClassTemplateParamCollector::collect(
|
2019-02-04 06:52:31 +01:00
|
|
|
$codebase,
|
|
|
|
$class_storage,
|
2020-05-27 05:29:37 +02:00
|
|
|
$class_storage,
|
2019-02-04 06:52:31 +01:00
|
|
|
$method_name_lc,
|
|
|
|
$lhs_type_part,
|
|
|
|
null
|
|
|
|
);
|
|
|
|
|
2019-06-24 23:45:10 +02:00
|
|
|
if ($found_generic_params
|
|
|
|
&& $stmt->class instanceof PhpParser\Node\Name
|
|
|
|
&& $stmt->class->parts === ['parent']
|
|
|
|
&& $context->self
|
2019-06-25 05:42:36 +02:00
|
|
|
&& ($self_class_storage = $codebase->classlike_storage_provider->get($context->self))
|
|
|
|
&& $self_class_storage->template_type_extends
|
2019-06-24 23:45:10 +02:00
|
|
|
) {
|
2019-06-25 05:42:36 +02:00
|
|
|
foreach ($self_class_storage->template_type_extends as $template_fq_class_name => $extended_types) {
|
2019-06-24 23:45:10 +02:00
|
|
|
foreach ($extended_types as $type_key => $extended_type) {
|
|
|
|
if (!is_string($type_key)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-06-25 05:42:36 +02:00
|
|
|
if (isset($found_generic_params[$type_key][$template_fq_class_name])) {
|
|
|
|
$found_generic_params[$type_key][$template_fq_class_name][0] = clone $extended_type;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-01-04 18:20:26 +01:00
|
|
|
foreach ($extended_type->getAtomicTypes() as $t) {
|
2019-06-25 05:42:36 +02:00
|
|
|
if ($t instanceof Type\Atomic\TTemplateParam
|
2019-12-30 13:25:27 +01:00
|
|
|
&& isset($found_generic_params[$t->param_name][$t->defining_class])
|
2019-06-25 05:42:36 +02:00
|
|
|
) {
|
|
|
|
$found_generic_params[$type_key][$template_fq_class_name] = [
|
2019-12-30 13:25:27 +01:00
|
|
|
$found_generic_params[$t->param_name][$t->defining_class][0]
|
2019-06-25 05:42:36 +02:00
|
|
|
];
|
2019-06-25 05:57:19 +02:00
|
|
|
} else {
|
|
|
|
$found_generic_params[$type_key][$template_fq_class_name] = [
|
|
|
|
clone $extended_type
|
|
|
|
];
|
|
|
|
break;
|
2019-06-25 05:42:36 +02:00
|
|
|
}
|
2019-06-24 23:45:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-04 03:27:40 +01:00
|
|
|
$template_result = new \Psalm\Internal\Type\TemplateResult([], $found_generic_params ?: []);
|
|
|
|
|
2018-01-29 00:29:38 +01:00
|
|
|
if (self::checkMethodArgs(
|
|
|
|
$method_id,
|
2018-09-26 00:37:24 +02:00
|
|
|
$args,
|
2019-11-04 03:27:40 +01:00
|
|
|
$template_result,
|
2018-01-29 00:29:38 +01:00
|
|
|
$context,
|
2018-11-11 18:01:14 +01:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
|
|
|
$statements_analyzer
|
2018-01-29 00:29:38 +01:00
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$fq_class_name = $stmt->class instanceof PhpParser\Node\Name && $stmt->class->parts === ['parent']
|
2018-11-11 18:01:14 +01:00
|
|
|
? (string) $statements_analyzer->getFQCLN()
|
2018-01-29 00:29:38 +01:00
|
|
|
: $fq_class_name;
|
|
|
|
|
|
|
|
$self_fq_class_name = $fq_class_name;
|
|
|
|
|
2019-02-27 23:06:44 +01:00
|
|
|
$return_type_candidate = null;
|
2018-01-29 00:29:38 +01:00
|
|
|
|
2019-02-27 23:06:44 +01:00
|
|
|
if ($codebase->methods->return_type_provider->has($fq_class_name)) {
|
2019-02-16 17:16:52 +01:00
|
|
|
$return_type_candidate = $codebase->methods->return_type_provider->getReturnType(
|
|
|
|
$statements_analyzer,
|
2019-02-27 23:06:44 +01:00
|
|
|
$fq_class_name,
|
|
|
|
$stmt->name->name,
|
2019-02-16 17:16:52 +01:00
|
|
|
$stmt->args,
|
|
|
|
$context,
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt->name)
|
2018-01-29 00:29:38 +01:00
|
|
|
);
|
2019-02-27 23:06:44 +01:00
|
|
|
}
|
|
|
|
|
2019-03-12 17:58:04 +01:00
|
|
|
$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
if (!$return_type_candidate
|
|
|
|
&& $declaring_method_id
|
|
|
|
&& (string) $declaring_method_id !== (string) $method_id
|
|
|
|
) {
|
|
|
|
$declaring_fq_class_name = $declaring_method_id->fq_class_name;
|
|
|
|
$declaring_method_name = $declaring_method_id->method_name;
|
2019-03-12 17:58:04 +01:00
|
|
|
|
|
|
|
if ($codebase->methods->return_type_provider->has($declaring_fq_class_name)) {
|
|
|
|
$return_type_candidate = $codebase->methods->return_type_provider->getReturnType(
|
|
|
|
$statements_analyzer,
|
|
|
|
$declaring_fq_class_name,
|
|
|
|
$declaring_method_name,
|
|
|
|
$stmt->args,
|
|
|
|
$context,
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt->name),
|
|
|
|
null,
|
|
|
|
$fq_class_name,
|
|
|
|
$stmt->name->name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-27 23:06:44 +01:00
|
|
|
if (!$return_type_candidate) {
|
2019-02-16 17:16:52 +01:00
|
|
|
$return_type_candidate = $codebase->methods->getMethodReturnType(
|
2018-01-29 00:29:38 +01:00
|
|
|
$method_id,
|
2019-02-16 17:16:52 +01:00
|
|
|
$self_fq_class_name,
|
2019-11-25 17:44:54 +01:00
|
|
|
$statements_analyzer,
|
2019-02-16 17:16:52 +01:00
|
|
|
$args
|
2018-01-29 00:29:38 +01:00
|
|
|
);
|
|
|
|
|
2019-02-16 17:16:52 +01:00
|
|
|
if ($return_type_candidate) {
|
|
|
|
$return_type_candidate = clone $return_type_candidate;
|
2018-01-29 00:29:38 +01:00
|
|
|
|
2019-11-07 17:03:41 +01:00
|
|
|
if ($template_result->template_types) {
|
|
|
|
$bindable_template_types = $return_type_candidate->getTemplateTypes();
|
|
|
|
|
|
|
|
foreach ($bindable_template_types as $template_type) {
|
|
|
|
if (!isset(
|
2020-04-07 06:13:56 +02:00
|
|
|
$template_result->upper_bounds
|
2019-11-07 17:03:41 +01:00
|
|
|
[$template_type->param_name]
|
2019-12-30 13:25:27 +01:00
|
|
|
[$template_type->defining_class]
|
2019-11-07 17:03:41 +01:00
|
|
|
)) {
|
2020-05-01 22:02:53 +02:00
|
|
|
if ($template_type->param_name === 'TFunctionArgCount') {
|
|
|
|
$template_result->upper_bounds[$template_type->param_name] = [
|
2020-05-25 15:39:30 +02:00
|
|
|
'fn-' . strtolower((string) $method_id) => [
|
|
|
|
Type::getInt(false, count($stmt->args)),
|
|
|
|
0
|
|
|
|
]
|
2020-05-01 22:02:53 +02:00
|
|
|
];
|
|
|
|
} else {
|
|
|
|
$template_result->upper_bounds[$template_type->param_name] = [
|
|
|
|
($template_type->defining_class) => [Type::getEmpty(), 0]
|
|
|
|
];
|
|
|
|
}
|
2019-11-07 17:03:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-24 21:08:55 +02:00
|
|
|
if ($lhs_type_part instanceof Type\Atomic\TTemplateParam) {
|
|
|
|
$static_type = $lhs_type_part;
|
|
|
|
} elseif ($lhs_type_part instanceof Type\Atomic\TTemplateParamClass) {
|
|
|
|
$static_type = new Type\Atomic\TTemplateParam(
|
|
|
|
$lhs_type_part->param_name,
|
|
|
|
$lhs_type_part->as_type
|
|
|
|
? new Type\Union([$lhs_type_part->as_type])
|
2019-12-09 03:12:18 +01:00
|
|
|
: Type::getObject(),
|
|
|
|
$lhs_type_part->defining_class
|
2019-06-24 21:08:55 +02:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$static_type = $fq_class_name;
|
|
|
|
}
|
|
|
|
|
2020-04-07 06:13:56 +02:00
|
|
|
if ($template_result->upper_bounds) {
|
2020-05-11 04:45:01 +02:00
|
|
|
$return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion(
|
2020-03-29 19:37:37 +02:00
|
|
|
$codebase,
|
|
|
|
$return_type_candidate,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null
|
|
|
|
);
|
|
|
|
|
|
|
|
$return_type_candidate->replaceTemplateTypesWithArgTypes(
|
2020-04-07 06:13:56 +02:00
|
|
|
$template_result,
|
2020-03-29 19:37:37 +02:00
|
|
|
$codebase
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-11 04:45:01 +02:00
|
|
|
$return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion(
|
2019-02-16 17:16:52 +01:00
|
|
|
$codebase,
|
|
|
|
$return_type_candidate,
|
|
|
|
$self_fq_class_name,
|
2019-06-24 21:08:55 +02:00
|
|
|
$static_type,
|
2020-04-22 17:34:38 +02:00
|
|
|
$class_storage->parent_class,
|
|
|
|
true,
|
|
|
|
false,
|
|
|
|
\is_string($static_type)
|
|
|
|
&& $static_type !== $context->self
|
2018-01-29 00:29:38 +01:00
|
|
|
);
|
2019-02-16 17:16:52 +01:00
|
|
|
|
|
|
|
$return_type_location = $codebase->methods->getMethodReturnTypeLocation(
|
|
|
|
$method_id,
|
|
|
|
$secondary_return_type_location
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($secondary_return_type_location) {
|
|
|
|
$return_type_location = $secondary_return_type_location;
|
|
|
|
}
|
|
|
|
|
|
|
|
// only check the type locally if it's defined externally
|
|
|
|
if ($return_type_location && !$config->isInProjectDirs($return_type_location->file_path)) {
|
|
|
|
$return_type_candidate->check(
|
|
|
|
$statements_analyzer,
|
|
|
|
new CodeLocation($source, $stmt),
|
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
2020-04-27 06:05:20 +02:00
|
|
|
$context->phantom_classes,
|
|
|
|
true,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
$context->calling_method_id
|
2019-02-16 17:16:52 +01:00
|
|
|
);
|
|
|
|
}
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-27 17:47:27 +02:00
|
|
|
$method_storage = $codebase->methods->getUserMethodStorage($method_id);
|
2018-07-11 17:32:12 +02:00
|
|
|
|
2018-10-27 17:47:27 +02:00
|
|
|
if ($method_storage) {
|
2020-03-11 15:36:39 +01:00
|
|
|
if ($method_storage->abstract
|
2020-03-23 12:40:19 +01:00
|
|
|
&& $stmt->class instanceof PhpParser\Node\Name
|
2020-03-11 15:36:39 +01:00
|
|
|
&& (!$context->self
|
2020-07-22 01:40:35 +02:00
|
|
|
|| !\Psalm\Internal\Type\Comparator\UnionTypeComparator::isContainedBy(
|
2020-03-11 15:36:39 +01:00
|
|
|
$codebase,
|
|
|
|
$context->vars_in_scope['$this']
|
|
|
|
?? new Type\Union([
|
|
|
|
new Type\Atomic\TNamedObject($context->self)
|
|
|
|
]),
|
|
|
|
new Type\Union([
|
|
|
|
new Type\Atomic\TNamedObject($method_id->fq_class_name)
|
|
|
|
])
|
|
|
|
))
|
|
|
|
) {
|
2020-03-11 15:18:40 +01:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new AbstractMethodCall(
|
2020-03-11 15:36:39 +01:00
|
|
|
'Cannot call an abstract static method ' . $method_id . ' directly',
|
2020-03-11 15:18:40 +01:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
2020-03-11 15:18:40 +01:00
|
|
|
}
|
|
|
|
|
2020-04-18 18:43:51 +02:00
|
|
|
if (!$context->inside_throw) {
|
|
|
|
if ($context->pure && !$method_storage->pure) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new ImpureMethodCall(
|
|
|
|
'Cannot call an impure method from a pure context',
|
|
|
|
new CodeLocation($source, $stmt->name)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
} elseif ($context->mutation_free && !$method_storage->mutation_free) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new ImpureMethodCall(
|
|
|
|
'Cannot call an possibly-mutating method from a mutation-free context',
|
|
|
|
new CodeLocation($source, $stmt->name)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
2019-08-30 18:36:35 +02:00
|
|
|
}
|
2019-07-18 07:31:48 +02:00
|
|
|
}
|
|
|
|
|
2020-04-07 06:13:56 +02:00
|
|
|
$generic_params = $template_result->upper_bounds;
|
2019-11-04 11:31:45 +01:00
|
|
|
|
2018-07-11 18:05:50 +02:00
|
|
|
if ($method_storage->assertions) {
|
|
|
|
self::applyAssertionsToContext(
|
2018-11-14 18:25:17 +01:00
|
|
|
$stmt->name,
|
2019-09-06 03:00:02 +02:00
|
|
|
null,
|
2018-07-11 18:05:50 +02:00
|
|
|
$method_storage->assertions,
|
|
|
|
$stmt->args,
|
2019-11-04 11:31:45 +01:00
|
|
|
$generic_params,
|
2018-07-11 18:05:50 +02:00
|
|
|
$context,
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer
|
2018-07-11 18:05:50 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($method_storage->if_true_assertions) {
|
2019-11-25 17:44:54 +01:00
|
|
|
$statements_analyzer->node_data->setIfTrueAssertions(
|
|
|
|
$stmt,
|
|
|
|
array_map(
|
|
|
|
function (Assertion $assertion) use ($generic_params) : Assertion {
|
2019-12-30 16:01:31 +01:00
|
|
|
return $assertion->getUntemplatedCopy($generic_params, null);
|
2019-11-25 17:44:54 +01:00
|
|
|
},
|
|
|
|
$method_storage->if_true_assertions
|
|
|
|
)
|
2019-01-23 05:42:54 +01:00
|
|
|
);
|
2018-07-11 18:05:50 +02:00
|
|
|
}
|
2018-07-11 17:32:12 +02:00
|
|
|
|
2018-07-11 18:05:50 +02:00
|
|
|
if ($method_storage->if_false_assertions) {
|
2019-11-25 17:44:54 +01:00
|
|
|
$statements_analyzer->node_data->setIfFalseAssertions(
|
|
|
|
$stmt,
|
|
|
|
array_map(
|
|
|
|
function (Assertion $assertion) use ($generic_params) : Assertion {
|
2019-12-30 16:01:31 +01:00
|
|
|
return $assertion->getUntemplatedCopy($generic_params, null);
|
2019-11-25 17:44:54 +01:00
|
|
|
},
|
|
|
|
$method_storage->if_false_assertions
|
|
|
|
)
|
2019-01-23 05:42:54 +01:00
|
|
|
);
|
2018-07-11 18:05:50 +02:00
|
|
|
}
|
2018-07-11 17:32:12 +02:00
|
|
|
}
|
|
|
|
|
2019-06-05 17:22:09 +02:00
|
|
|
if ($codebase->alter_code) {
|
2019-06-14 20:41:36 +02:00
|
|
|
foreach ($codebase->call_transforms as $original_pattern => $transformation) {
|
|
|
|
if ($declaring_method_id
|
2020-02-15 02:54:26 +01:00
|
|
|
&& strtolower((string) $declaring_method_id) . '\((.*\))' === $original_pattern
|
2019-06-14 20:41:36 +02:00
|
|
|
) {
|
|
|
|
if (strpos($transformation, '($1)') === strlen($transformation) - 4
|
|
|
|
&& $stmt->class instanceof PhpParser\Node\Name
|
2019-06-05 17:22:09 +02:00
|
|
|
) {
|
2019-06-14 20:41:36 +02:00
|
|
|
$new_method_id = substr($transformation, 0, -4);
|
2020-02-15 02:54:26 +01:00
|
|
|
$old_declaring_fq_class_name = $declaring_method_id->fq_class_name;
|
2019-06-14 20:41:36 +02:00
|
|
|
list($new_fq_class_name, $new_method_name) = explode('::', $new_method_id);
|
2019-06-01 16:07:38 +02:00
|
|
|
|
2019-06-14 20:41:36 +02:00
|
|
|
if ($codebase->classlikes->handleClassLikeReferenceInMigration(
|
|
|
|
$codebase,
|
|
|
|
$statements_analyzer,
|
|
|
|
$stmt->class,
|
|
|
|
$new_fq_class_name,
|
2020-03-26 17:35:27 +01:00
|
|
|
$context->calling_method_id,
|
2019-11-17 01:59:08 +01:00
|
|
|
strtolower($old_declaring_fq_class_name) !== strtolower($new_fq_class_name),
|
|
|
|
$stmt->class->parts[0] === 'self'
|
2019-06-14 20:41:36 +02:00
|
|
|
)) {
|
2019-06-05 17:22:09 +02:00
|
|
|
$moved_call = true;
|
|
|
|
}
|
2019-06-14 20:41:36 +02:00
|
|
|
|
|
|
|
$file_manipulations = [];
|
|
|
|
|
|
|
|
$file_manipulations[] = new \Psalm\FileManipulation(
|
|
|
|
(int) $stmt->name->getAttribute('startFilePos'),
|
|
|
|
(int) $stmt->name->getAttribute('endFilePos') + 1,
|
|
|
|
$new_method_name
|
|
|
|
);
|
|
|
|
|
|
|
|
FileManipulationBuffer::add(
|
|
|
|
$statements_analyzer->getFilePath(),
|
|
|
|
$file_manipulations
|
|
|
|
);
|
2019-06-05 17:22:09 +02:00
|
|
|
}
|
2019-06-01 06:56:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-12 04:49:19 +01:00
|
|
|
if ($config->after_method_checks) {
|
|
|
|
$file_manipulations = [];
|
|
|
|
|
2019-02-27 23:06:44 +01:00
|
|
|
$appearing_method_id = $codebase->methods->getAppearingMethodId($method_id);
|
|
|
|
|
2020-02-15 02:54:26 +01:00
|
|
|
if ($appearing_method_id !== null && $declaring_method_id) {
|
2018-11-23 19:24:35 +01:00
|
|
|
foreach ($config->after_method_checks as $plugin_fq_class_name) {
|
|
|
|
$plugin_fq_class_name::afterMethodCallAnalysis(
|
|
|
|
$stmt,
|
2020-02-15 02:54:26 +01:00
|
|
|
(string) $method_id,
|
|
|
|
(string) $appearing_method_id,
|
|
|
|
(string) $declaring_method_id,
|
2018-11-23 19:24:35 +01:00
|
|
|
$context,
|
|
|
|
$source,
|
|
|
|
$codebase,
|
|
|
|
$file_manipulations,
|
|
|
|
$return_type_candidate
|
|
|
|
);
|
|
|
|
}
|
2018-02-12 04:49:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($file_manipulations) {
|
2018-11-11 18:01:14 +01:00
|
|
|
FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
|
2018-02-12 04:49:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-23 21:52:19 +02:00
|
|
|
$return_type_candidate = $return_type_candidate ?: Type::getMixed();
|
|
|
|
|
|
|
|
self::taintReturnType(
|
|
|
|
$statements_analyzer,
|
|
|
|
$stmt,
|
|
|
|
$method_id,
|
|
|
|
$cased_method_id,
|
|
|
|
$return_type_candidate,
|
|
|
|
$method_storage
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($stmt_type = $statements_analyzer->node_data->getType($stmt)) {
|
|
|
|
$statements_analyzer->node_data->setType(
|
2020-06-22 00:07:39 +02:00
|
|
|
$stmt,
|
2020-06-23 21:52:19 +02:00
|
|
|
Type::combineUnionTypes($stmt_type, $return_type_candidate)
|
2020-06-22 00:07:39 +02:00
|
|
|
);
|
2020-06-23 21:52:19 +02:00
|
|
|
} else {
|
|
|
|
$statements_analyzer->node_data->setType($stmt, $return_type_candidate);
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
2018-06-27 05:11:16 +02:00
|
|
|
} else {
|
2019-07-25 15:45:11 +02:00
|
|
|
if ($stmt->name instanceof PhpParser\Node\Expr) {
|
|
|
|
ExpressionAnalyzer::analyze($statements_analyzer, $stmt->name, $context);
|
|
|
|
}
|
|
|
|
|
2019-04-20 23:49:49 +02:00
|
|
|
if (!$context->ignore_variable_method) {
|
2019-04-27 23:38:24 +02:00
|
|
|
$codebase->analyzer->addMixedMemberName(
|
|
|
|
strtolower($fq_class_name) . '::',
|
2020-03-26 17:35:27 +01:00
|
|
|
$context->calling_method_id ?: $statements_analyzer->getFileName()
|
2019-04-27 23:38:24 +02:00
|
|
|
);
|
2019-04-20 23:49:49 +02:00
|
|
|
}
|
2019-04-19 17:45:08 +02:00
|
|
|
|
2020-05-19 04:57:00 +02:00
|
|
|
if (ArgumentsAnalyzer::analyze(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2018-06-27 05:11:16 +02:00
|
|
|
$stmt->args,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
$context
|
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
2018-10-26 22:17:15 +02:00
|
|
|
|
2019-11-15 21:34:25 +01:00
|
|
|
if ($codebase->alter_code
|
|
|
|
&& $fq_class_name
|
|
|
|
&& !$moved_call
|
|
|
|
&& $stmt->class instanceof PhpParser\Node\Name
|
|
|
|
&& !in_array($stmt->class->parts[0], ['parent', 'static'])
|
|
|
|
) {
|
2019-06-14 20:41:36 +02:00
|
|
|
$codebase->classlikes->handleClassLikeReferenceInMigration(
|
|
|
|
$codebase,
|
|
|
|
$statements_analyzer,
|
|
|
|
$stmt->class,
|
|
|
|
$fq_class_name,
|
2020-03-26 17:35:27 +01:00
|
|
|
$context->calling_method_id,
|
2019-11-17 01:59:08 +01:00
|
|
|
false,
|
|
|
|
$stmt->class->parts[0] === 'self'
|
2019-06-14 20:41:36 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-07-01 15:55:39 +02:00
|
|
|
if ($codebase->store_node_types
|
|
|
|
&& $method_id
|
|
|
|
&& !$context->collect_initializations
|
|
|
|
&& !$context->collect_mutations
|
|
|
|
) {
|
2018-10-26 22:17:15 +02:00
|
|
|
$codebase->analyzer->addNodeReference(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getFilePath(),
|
2018-10-26 22:17:15 +02:00
|
|
|
$stmt->name,
|
|
|
|
$method_id . '()'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-02-24 07:33:25 +01:00
|
|
|
if ($codebase->store_node_types
|
2019-07-01 15:55:39 +02:00
|
|
|
&& !$context->collect_initializations
|
|
|
|
&& !$context->collect_mutations
|
2019-11-25 17:44:54 +01:00
|
|
|
&& ($stmt_type = $statements_analyzer->node_data->getType($stmt))
|
2018-10-26 22:17:15 +02:00
|
|
|
) {
|
|
|
|
$codebase->analyzer->addNodeType(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer->getFilePath(),
|
2018-10-26 22:17:15 +02:00
|
|
|
$stmt->name,
|
2020-02-23 23:03:27 +01:00
|
|
|
$stmt_type->getId(),
|
2019-05-17 18:38:29 +02:00
|
|
|
$stmt
|
2018-10-26 22:17:15 +02:00
|
|
|
);
|
|
|
|
}
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($method_id === null) {
|
|
|
|
return self::checkMethodArgs(
|
|
|
|
$method_id,
|
|
|
|
$stmt->args,
|
2019-11-04 03:27:40 +01:00
|
|
|
null,
|
2018-01-29 00:29:38 +01:00
|
|
|
$context,
|
2018-11-11 18:01:14 +01:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
|
|
|
$statements_analyzer
|
2018-01-29 00:29:38 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$config->remember_property_assignments_after_call && !$context->collect_initializations) {
|
|
|
|
$context->removeAllObjectVars();
|
|
|
|
}
|
2019-11-14 20:44:29 +01:00
|
|
|
|
2019-11-25 17:44:54 +01:00
|
|
|
if (!$statements_analyzer->node_data->getType($stmt)) {
|
|
|
|
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
|
2019-11-14 20:44:29 +01:00
|
|
|
}
|
2020-05-18 21:13:27 +02:00
|
|
|
|
|
|
|
return true;
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|
2020-02-02 00:21:32 +01:00
|
|
|
|
2020-06-22 00:07:39 +02:00
|
|
|
private static function taintReturnType(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
PhpParser\Node\Expr\StaticCall $stmt,
|
|
|
|
MethodIdentifier $method_id,
|
|
|
|
string $cased_method_id,
|
|
|
|
Type\Union $return_type_candidate,
|
|
|
|
?\Psalm\Storage\MethodStorage $method_storage
|
|
|
|
) : void {
|
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
|
|
|
|
|
|
|
if (!$codebase->taint
|
|
|
|
|| !$codebase->config->trackTaintsInPath($statements_analyzer->getFilePath())
|
2020-07-02 17:53:44 +02:00
|
|
|
|| \in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())
|
2020-06-22 00:07:39 +02:00
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$code_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
|
|
|
|
|
|
|
|
$method_location = $method_storage
|
|
|
|
? ($method_storage->signature_return_type_location ?: $method_storage->location)
|
|
|
|
: null;
|
|
|
|
|
2020-06-23 00:40:35 +02:00
|
|
|
if ($method_storage && $method_storage->specialize_call) {
|
2020-06-22 00:07:39 +02:00
|
|
|
$method_source = TaintNode::getForMethodReturn(
|
|
|
|
(string) $method_id,
|
|
|
|
$cased_method_id,
|
|
|
|
$method_location,
|
|
|
|
$code_location
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$method_source = TaintNode::getForMethodReturn(
|
|
|
|
(string) $method_id,
|
|
|
|
$cased_method_id,
|
|
|
|
$method_location
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$codebase->taint->addTaintNode($method_source);
|
|
|
|
|
|
|
|
$return_type_candidate->parent_nodes = [$method_source];
|
|
|
|
|
|
|
|
if ($method_storage && $method_storage->taint_source_types) {
|
|
|
|
$method_node = Source::getForMethodReturn(
|
|
|
|
(string) $method_id,
|
|
|
|
$cased_method_id,
|
|
|
|
$method_storage->signature_return_type_location ?: $method_storage->location
|
|
|
|
);
|
|
|
|
|
|
|
|
$method_node->taints = $method_storage->taint_source_types;
|
|
|
|
|
|
|
|
$codebase->taint->addSource($method_node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-02 00:21:32 +01:00
|
|
|
/**
|
2020-02-02 00:24:09 +01:00
|
|
|
* @param array<int, PhpParser\Node\Arg> $args
|
2020-02-02 00:21:32 +01:00
|
|
|
* @return false|null
|
|
|
|
*/
|
|
|
|
private static function checkPseudoMethod(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
PhpParser\Node\Expr\StaticCall $stmt,
|
2020-06-10 00:39:52 +02:00
|
|
|
MethodIdentifier $method_id,
|
2020-02-02 00:21:32 +01:00
|
|
|
string $fq_class_name,
|
|
|
|
array $args,
|
|
|
|
\Psalm\Storage\ClassLikeStorage $class_storage,
|
|
|
|
\Psalm\Storage\MethodStorage $pseudo_method_storage,
|
|
|
|
Context $context
|
|
|
|
) {
|
2020-05-19 04:57:00 +02:00
|
|
|
if (ArgumentsAnalyzer::analyze(
|
2020-02-02 00:21:32 +01:00
|
|
|
$statements_analyzer,
|
|
|
|
$args,
|
|
|
|
$pseudo_method_storage->params,
|
2020-02-15 02:54:26 +01:00
|
|
|
(string) $method_id,
|
2020-02-02 00:21:32 +01:00
|
|
|
$context
|
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-06-22 00:07:39 +02:00
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
|
|
|
|
2020-05-19 04:57:00 +02:00
|
|
|
if (ArgumentsAnalyzer::checkArgumentsMatch(
|
2020-02-02 00:21:32 +01:00
|
|
|
$statements_analyzer,
|
|
|
|
$args,
|
2020-06-21 08:06:08 +02:00
|
|
|
$method_id,
|
2020-02-02 00:21:32 +01:00
|
|
|
$pseudo_method_storage->params,
|
|
|
|
$pseudo_method_storage,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
new CodeLocation($statements_analyzer, $stmt),
|
|
|
|
$context
|
|
|
|
) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-06-22 00:07:39 +02:00
|
|
|
$method_storage = null;
|
|
|
|
|
|
|
|
if ($codebase->taint) {
|
|
|
|
try {
|
|
|
|
$method_storage = $codebase->methods->getStorage($method_id);
|
|
|
|
|
|
|
|
ArgumentsAnalyzer::analyze(
|
|
|
|
$statements_analyzer,
|
|
|
|
$args,
|
|
|
|
$method_storage->params,
|
|
|
|
(string) $method_id,
|
|
|
|
$context
|
|
|
|
);
|
|
|
|
|
|
|
|
ArgumentsAnalyzer::checkArgumentsMatch(
|
|
|
|
$statements_analyzer,
|
|
|
|
$args,
|
|
|
|
$method_id,
|
|
|
|
$method_storage->params,
|
|
|
|
$method_storage,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
new CodeLocation($statements_analyzer, $stmt),
|
|
|
|
$context
|
|
|
|
);
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-02 00:21:32 +01:00
|
|
|
if ($pseudo_method_storage->return_type) {
|
|
|
|
$return_type_candidate = clone $pseudo_method_storage->return_type;
|
|
|
|
|
2020-05-11 04:45:01 +02:00
|
|
|
$return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion(
|
2020-02-02 00:21:32 +01:00
|
|
|
$statements_analyzer->getCodebase(),
|
|
|
|
$return_type_candidate,
|
|
|
|
$fq_class_name,
|
|
|
|
$fq_class_name,
|
|
|
|
$class_storage->parent_class
|
|
|
|
);
|
|
|
|
|
2020-06-22 00:07:39 +02:00
|
|
|
if ($method_storage) {
|
|
|
|
self::taintReturnType(
|
|
|
|
$statements_analyzer,
|
|
|
|
$stmt,
|
|
|
|
$method_id,
|
|
|
|
(string) $method_id,
|
|
|
|
$return_type_candidate,
|
|
|
|
$method_storage
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-02-02 00:21:32 +01:00
|
|
|
$stmt_type = $statements_analyzer->node_data->getType($stmt);
|
|
|
|
|
|
|
|
if (!$stmt_type) {
|
|
|
|
$statements_analyzer->node_data->setType($stmt, $return_type_candidate);
|
|
|
|
} else {
|
|
|
|
$statements_analyzer->node_data->setType(
|
|
|
|
$stmt,
|
|
|
|
Type::combineUnionTypes(
|
|
|
|
$return_type_candidate,
|
|
|
|
$stmt_type
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-01-29 00:29:38 +01:00
|
|
|
}
|