1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-17 03:47:04 +01:00
psalm/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php

1338 lines
56 KiB
PHP
Raw Normal View History

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;
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
2018-11-06 03:57:36 +01:00
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
2018-01-29 00:29:38 +01:00
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
use Psalm\Issue\AbstractMethodCall;
2018-01-29 00:29:38 +01:00
use Psalm\Issue\DeprecatedClass;
use Psalm\Issue\ImpureMethodCall;
use Psalm\Issue\InvalidStringClass;
use Psalm\Issue\InternalClass;
use Psalm\Issue\MixedMethodCall;
use Psalm\Issue\NonStaticSelfCall;
2018-01-29 00:29:38 +01:00
use Psalm\Issue\ParentNotFound;
use Psalm\Issue\UndefinedClass;
use Psalm\Issue\UndefinedMethod;
2018-01-29 00:29:38 +01:00
use Psalm\IssueBuffer;
use Psalm\Storage\Assertion;
2018-01-29 00:29:38 +01:00
use Psalm\Type;
use Psalm\Type\Atomic\TNamedObject;
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;
2019-08-14 06:47:57 +02:00
use Psalm\Internal\Taint\Source;
2018-01-29 00:29:38 +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;
$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
$stmt_type = null;
2018-01-29 00:29:38 +01:00
2018-11-06 03:57:36 +01:00
$config = $codebase->config;
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;
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
}
$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;
} 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 {
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();
if ($context->calling_method_id
&& !$stmt->class instanceof PhpParser\Node\Name\FullyQualified
) {
$codebase->file_reference_provider->addMethodReferenceToClassMember(
$context->calling_method_id,
2018-11-11 18:01:14 +01:00
'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath())
);
}
2018-11-06 03:57:36 +01:00
$fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
2018-01-29 00:29:38 +01:00
$stmt->class,
$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;
}
}
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(),
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
}
}
if ($codebase->store_node_types
&& $fq_class_name
&& !$context->collect_initializations
&& !$context->collect_mutations
) {
$codebase->analyzer->addNodeReference(
2018-11-11 18:01:14 +01:00
$statements_analyzer->getFilePath(),
$stmt->class,
$fq_class_name
);
}
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);
$lhs_type = $statements_analyzer->node_data->getType($stmt->class) ?: Type::getMixed();
2018-01-29 00:29:38 +01:00
}
if (!$lhs_type) {
if (self::checkFunctionArguments(
2018-11-11 18:01:14 +01:00
$statements_analyzer,
$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;
$moved_call = false;
2018-01-29 00:29:38 +01:00
foreach ($lhs_type->getAtomicTypes() as $lhs_type_part) {
$intersection_types = [];
if ($lhs_type_part instanceof TNamedObject) {
$fq_class_name = $lhs_type_part->value;
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,
$statements_analyzer->getSuppressedIssues(),
$stmt->class instanceof PhpParser\Node\Name
2020-01-01 18:49:12 +01:00
&& count($stmt->class->parts) === 1
&& in_array(strtolower($stmt->class->parts[0]), ['self', 'static'], true)
)) {
return false;
}
$intersection_types = $lhs_type_part->extra_types;
} elseif ($lhs_type_part instanceof Type\Atomic\TClassString
&& $lhs_type_part->as_type
) {
$fq_class_name = $lhs_type_part->as_type->value;
if (!ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$statements_analyzer,
$fq_class_name,
new CodeLocation($source, $stmt->class),
$context->self,
$context->calling_method_id,
$statements_analyzer->getSuppressedIssues(),
false
)) {
return false;
}
$intersection_types = $lhs_type_part->as_type->extra_types;
} 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()
&& $lhs_type_part->as_type->isSingle()
) {
foreach ($lhs_type_part->as_type->getAtomicTypes() as $typeof_type_atomic) {
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;
}
} elseif ($lhs_type_part instanceof Type\Atomic\TLiteralClassString) {
$fq_class_name = $lhs_type_part->value;
if (!ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$statements_analyzer,
$fq_class_name,
new CodeLocation($source, $stmt->class),
$context->self,
$context->calling_method_id,
$statements_analyzer->getSuppressedIssues(),
false
)) {
return false;
}
2019-02-22 03:40:06 +01:00
} elseif ($lhs_type_part instanceof Type\Atomic\TTemplateParam
&& !$lhs_type_part->as->isMixed()
&& !$lhs_type_part->as->hasObject()
) {
$fq_class_name = null;
foreach ($lhs_type_part->as->getAtomicTypes() as $generic_param_type) {
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;
}
} else {
if ($lhs_type_part instanceof Type\Atomic\TMixed
2019-02-22 03:40:06 +01:00
|| $lhs_type_part instanceof Type\Atomic\TTemplateParam
|| $lhs_type_part instanceof Type\Atomic\TClassString
) {
if ($stmt->name instanceof PhpParser\Node\Identifier) {
2019-04-27 23:38:24 +02:00
$codebase->analyzer->addMixedMemberName(
strtolower($stmt->name->name),
$context->calling_method_id ?: $statements_analyzer->getFileName()
2019-04-27 23:38:24 +02:00
);
}
if (IssueBuffer::accepts(
new MixedMethodCall(
'Cannot call method on an unknown class',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
continue;
}
if ($lhs_type_part instanceof Type\Atomic\TString) {
if ($config->allow_string_standin_for_class
&& !$lhs_type_part instanceof Type\Atomic\TNumericString
) {
continue;
}
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-11-11 18:01:14 +01:00
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
continue;
}
if ($lhs_type_part instanceof Type\Atomic\TNull
&& $lhs_type->ignore_nullable_issues
) {
continue;
}
if (IssueBuffer::accepts(
new UndefinedClass(
'Type ' . $lhs_type_part . ' cannot be called as a class',
2018-11-11 18:01:14 +01:00
new CodeLocation($statements_analyzer->getSource(), $stmt),
(string) $lhs_type_part
),
2018-11-11 18:01:14 +01:00
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
2018-01-29 00:29:38 +01:00
continue;
}
$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;
if ($stmt->name instanceof PhpParser\Node\Identifier && !$is_mock) {
$method_name_lc = strtolower($stmt->name->name);
$method_id = new \Psalm\Internal\MethodIdentifier($fq_class_name, $method_name_lc);
$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
&& !$context->collect_mutations
) {
ArgumentMapPopulator::recordArgumentPositions(
$statements_analyzer,
$stmt,
$codebase,
(string) $method_id
);
}
$args = $stmt->args;
if ($intersection_types
&& !$codebase->methods->methodExists($method_id)
) {
foreach ($intersection_types as $intersection_type) {
if (!$intersection_type instanceof TNamedObject) {
continue;
}
$intersection_method_id = new \Psalm\Internal\MethodIdentifier(
$intersection_type->value,
$method_name_lc
);
if ($codebase->methods->methodExists($intersection_method_id)) {
$method_id = $intersection_method_id;
$cased_method_id = $intersection_type->value . '::' . $stmt->name->name;
$fq_class_name = $intersection_type->value;
break;
}
}
}
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
if (!$codebase->methods->methodExists(
$method_id,
2020-03-28 19:45:58 +01:00
!$context->collect_initializations
&& !$context->collect_mutations
? $context->calling_method_id
: null,
$codebase->collect_locations
2020-03-28 19:45:58 +01:00
? new CodeLocation($source, $stmt->name)
: null,
$statements_analyzer,
$statements_analyzer->getFilePath()
)
2018-11-06 03:57:36 +01:00
|| !MethodAnalyzer::isMethodVisible(
$method_id,
$context,
2018-11-11 18:01:14 +01:00
$statements_analyzer->getSource()
)
|| (isset($class_storage->pseudo_static_methods[$method_name_lc])
&& ($config->use_phpdoc_method_without_magic_or_parent || $class_storage->parent_class))
) {
$callstatic_id = new \Psalm\Internal\MethodIdentifier(
$fq_class_name,
'__callstatic'
);
if ($codebase->methods->methodExists(
$callstatic_id,
$context->calling_method_id,
$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,
$statements_analyzer->getFilePath()
)) {
if (isset($class_storage->pseudo_static_methods[$method_name_lc])) {
$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;
}
} else {
if (self::checkFunctionArguments(
$statements_analyzer,
$args,
null,
null,
$context
) === false) {
return false;
}
}
$array_values = array_map(
/**
* @return PhpParser\Node\Expr\ArrayItem
*/
function (PhpParser\Node\Arg $arg) {
return new PhpParser\Node\Expr\ArrayItem($arg->value);
},
$args
);
$args = [
new PhpParser\Node\Arg(new PhpParser\Node\Scalar\String_((string) $method_id)),
new PhpParser\Node\Arg(new PhpParser\Node\Expr\Array_($array_values)),
];
$method_id = new \Psalm\Internal\MethodIdentifier(
$fq_class_name,
'__callstatic'
);
} 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;
}
}
if (!$context->check_methods) {
if (self::checkFunctionArguments(
$statements_analyzer,
$stmt->args,
null,
null,
$context
) === false) {
return false;
}
2020-05-18 21:13:27 +02:00
return true;
}
}
2018-11-06 03:57:36 +01:00
$does_method_exist = MethodAnalyzer::checkMethodExists(
$codebase,
$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(),
$context->calling_method_id
2018-01-29 00:29:38 +01:00
);
if (!$does_method_exist) {
2019-03-01 05:59:17 +01:00
if (self::checkFunctionArguments(
$statements_analyzer,
$stmt->args,
null,
null,
$context
) === false) {
return false;
}
if ($codebase->alter_code && $fq_class_name && !$moved_call) {
$codebase->classlikes->handleClassLikeReferenceInMigration(
$codebase,
$statements_analyzer,
$stmt->class,
$fq_class_name,
$context->calling_method_id
);
}
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
if ($class_storage->user_defined
&& $context->self
&& ($context->collect_mutations || $context->collect_initializations)
) {
$appearing_method_id = $codebase->methods->getAppearingMethodId($method_id);
if (!$appearing_method_id) {
if (IssueBuffer::accepts(
new UndefinedMethod(
'Method ' . $method_id . ' does not exist',
new CodeLocation($statements_analyzer->getSource(), $stmt),
(string) $method_id
),
$statements_analyzer->getSuppressedIssues()
)) {
//
}
2020-05-18 21:13:27 +02:00
return true;
}
$appearing_method_class_name = $appearing_method_id->fq_class_name;
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];
}
}
if (!isset($context->initialized_methods[(string) $method_id])) {
if ($context->initialized_methods === null) {
$context->initialized_methods = [];
}
$context->initialized_methods[(string) $method_id] = true;
$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;
}
}
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',
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
}
}
if ($class_storage->psalm_internal
&& $context->self
&& ! NamespaceAnalyzer::isWithin($context->self, $class_storage->psalm_internal)
2019-05-11 18:26:42 +02:00
) {
if (IssueBuffer::accepts(
new InternalClass(
$fq_class_name . ' is marked internal to ' . $class_storage->psalm_internal,
new CodeLocation($statements_analyzer->getSource(), $stmt),
$fq_class_name
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
if ($class_storage->internal
&& $context->self
&& !$context->collect_initializations
&& !$context->collect_mutations
) {
if (! NamespaceAnalyzer::nameSpaceRootsMatch($context->self, $fq_class_name)) {
if (IssueBuffer::accepts(
new InternalClass(
$fq_class_name . ' is marked internal',
new CodeLocation($statements_analyzer->getSource(), $stmt),
$fq_class_name
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
}
2020-03-12 04:38:49 +01:00
if (MethodVisibilityAnalyzer::analyze(
2018-01-29 00:29:38 +01:00
$method_id,
$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;
}
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,
($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(),
$is_dynamic_this_method
2018-01-29 00:29:38 +01:00
) === false) {
// fall through
}
if ($is_dynamic_this_method) {
$old_data_provider = $statements_analyzer->node_data;
$statements_analyzer->node_data = clone $statements_analyzer->node_data;
$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,
$fake_method_call_expr,
$context
) === false) {
return false;
}
$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);
}
2020-05-18 21:13:27 +02:00
return true;
}
2018-01-29 00:29:38 +01:00
}
2020-03-12 04:38:49 +01:00
if (MethodCallProhibitionAnalyzer::analyze(
2018-11-06 03:57:36 +01:00
$codebase,
$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(
$codebase,
$class_storage,
2019-03-28 15:43:49 +01:00
$fq_class_name,
$method_name_lc,
$lhs_type_part,
null
);
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-25 05:42:36 +02:00
foreach ($self_class_storage->template_type_extends as $template_fq_class_name => $extended_types) {
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;
}
foreach ($extended_type->getAtomicTypes() as $t) {
2019-06-25 05:42:36 +02:00
if ($t instanceof Type\Atomic\TTemplateParam
&& 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] = [
$found_generic_params[$t->param_name][$t->defining_class][0]
2019-06-25 05:42:36 +02:00
];
} else {
$found_generic_params[$type_key][$template_fq_class_name] = [
clone $extended_type
];
break;
2019-06-25 05:42:36 +02:00
}
}
}
}
}
$template_result = new \Psalm\Internal\Type\TemplateResult([], $found_generic_params ?: []);
2018-01-29 00:29:38 +01:00
if (self::checkMethodArgs(
$method_id,
$args,
$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;
$return_type_candidate = null;
2018-01-29 00:29:38 +01:00
if ($codebase->methods->return_type_provider->has($fq_class_name)) {
$return_type_candidate = $codebase->methods->return_type_provider->getReturnType(
$statements_analyzer,
$fq_class_name,
$stmt->name->name,
$stmt->args,
$context,
new CodeLocation($statements_analyzer->getSource(), $stmt->name)
2018-01-29 00:29:38 +01:00
);
}
$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
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;
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
);
}
}
if (!$return_type_candidate) {
$return_type_candidate = $codebase->methods->getMethodReturnType(
2018-01-29 00:29:38 +01:00
$method_id,
$self_fq_class_name,
$statements_analyzer,
$args
2018-01-29 00:29:38 +01:00
);
if ($return_type_candidate) {
$return_type_candidate = clone $return_type_candidate;
2018-01-29 00:29:38 +01:00
if ($template_result->template_types) {
$bindable_template_types = $return_type_candidate->getTemplateTypes();
foreach ($bindable_template_types as $template_type) {
if (!isset(
$template_result->upper_bounds
[$template_type->param_name]
[$template_type->defining_class]
)) {
if ($template_type->param_name === 'TFunctionArgCount') {
$template_result->upper_bounds[$template_type->param_name] = [
'fn-' . $method_id => [Type::getInt(false, count($stmt->args)), 0]
];
} else {
$template_result->upper_bounds[$template_type->param_name] = [
($template_type->defining_class) => [Type::getEmpty(), 0]
];
}
}
}
}
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])
: Type::getObject(),
$lhs_type_part->defining_class
);
} else {
$static_type = $fq_class_name;
}
if ($template_result->upper_bounds) {
2020-05-11 04:45:01 +02:00
$return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion(
$codebase,
$return_type_candidate,
null,
null,
null
);
$return_type_candidate->replaceTemplateTypesWithArgTypes(
$template_result,
$codebase
);
}
2020-05-11 04:45:01 +02:00
$return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion(
$codebase,
$return_type_candidate,
$self_fq_class_name,
$static_type,
$class_storage->parent_class,
true,
false,
\is_string($static_type)
&& $static_type !== $context->self
2018-01-29 00:29:38 +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(),
$context->phantom_classes,
true,
false,
false,
$context->calling_method_id
);
}
2018-01-29 00:29:38 +01:00
}
}
$method_storage = $codebase->methods->getUserMethodStorage($method_id);
if ($method_storage) {
2020-03-11 15:36:39 +01:00
if ($method_storage->abstract
&& $stmt->class instanceof PhpParser\Node\Name
2020-03-11 15:36:39 +01:00
&& (!$context->self
|| !\Psalm\Internal\Analyzer\TypeAnalyzer::isContainedBy(
$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)
])
))
) {
if (IssueBuffer::accepts(
new AbstractMethodCall(
2020-03-11 15:36:39 +01:00
'Cannot call an abstract static method ' . $method_id . ' directly',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
2020-05-18 21:13:27 +02:00
return true;
}
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
}
}
$generic_params = $template_result->upper_bounds;
if ($method_storage->assertions) {
self::applyAssertionsToContext(
$stmt->name,
null,
$method_storage->assertions,
$stmt->args,
$generic_params,
$context,
2018-11-11 18:01:14 +01:00
$statements_analyzer
);
}
if ($method_storage->if_true_assertions) {
$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);
},
$method_storage->if_true_assertions
)
);
}
if ($method_storage->if_false_assertions) {
$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);
},
$method_storage->if_false_assertions
)
);
}
}
2019-06-05 17:22:09 +02:00
if ($codebase->alter_code) {
foreach ($codebase->call_transforms as $original_pattern => $transformation) {
if ($declaring_method_id
&& strtolower((string) $declaring_method_id) . '\((.*\))' === $original_pattern
) {
if (strpos($transformation, '($1)') === strlen($transformation) - 4
&& $stmt->class instanceof PhpParser\Node\Name
2019-06-05 17:22:09 +02:00
) {
$new_method_id = substr($transformation, 0, -4);
$old_declaring_fq_class_name = $declaring_method_id->fq_class_name;
list($new_fq_class_name, $new_method_name) = explode('::', $new_method_id);
if ($codebase->classlikes->handleClassLikeReferenceInMigration(
$codebase,
$statements_analyzer,
$stmt->class,
$new_fq_class_name,
$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-05 17:22:09 +02:00
$moved_call = true;
}
$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
}
}
}
}
if ($config->after_method_checks) {
$file_manipulations = [];
$appearing_method_id = $codebase->methods->getAppearingMethodId($method_id);
if ($appearing_method_id !== null && $declaring_method_id) {
foreach ($config->after_method_checks as $plugin_fq_class_name) {
$plugin_fq_class_name::afterMethodCallAnalysis(
$stmt,
(string) $method_id,
(string) $appearing_method_id,
(string) $declaring_method_id,
$context,
$source,
$codebase,
$file_manipulations,
$return_type_candidate
);
}
}
if ($file_manipulations) {
2018-11-11 18:01:14 +01:00
FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
}
}
2018-01-29 00:29:38 +01:00
if ($return_type_candidate) {
if ($codebase->taint) {
if ($method_storage && $method_storage->pure) {
$code_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
2019-08-14 06:47:57 +02:00
$method_source = new Source(
2019-08-06 23:29:44 +02:00
strtolower(
$method_id
. '-' . $code_location->file_name
. ':' . $code_location->raw_file_start
2019-08-07 00:56:36 +02:00
),
$cased_method_id,
new CodeLocation($source, $stmt->name)
);
} else {
2019-08-14 06:47:57 +02:00
$method_source = new Source(
strtolower((string) $method_id),
$cased_method_id,
new CodeLocation($source, $stmt->name)
);
}
2019-10-14 23:10:30 +02:00
$return_type_candidate->sources = [$method_source];
}
if ($stmt_type = $statements_analyzer->node_data->getType($stmt)) {
$statements_analyzer->node_data->setType(
$stmt,
Type::combineUnionTypes($stmt_type, $return_type_candidate)
);
2018-01-29 00:29:38 +01:00
} else {
$statements_analyzer->node_data->setType($stmt, $return_type_candidate);
2018-01-29 00:29:38 +01:00
}
}
} else {
if ($stmt->name instanceof PhpParser\Node\Expr) {
ExpressionAnalyzer::analyze($statements_analyzer, $stmt->name, $context);
}
if (!$context->ignore_variable_method) {
2019-04-27 23:38:24 +02:00
$codebase->analyzer->addMixedMemberName(
strtolower($fq_class_name) . '::',
$context->calling_method_id ?: $statements_analyzer->getFileName()
2019-04-27 23:38:24 +02:00
);
}
if (self::checkFunctionArguments(
2018-11-11 18:01:14 +01:00
$statements_analyzer,
$stmt->args,
null,
null,
$context
) === false) {
return false;
}
2018-01-29 00:29:38 +01: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'])
) {
$codebase->classlikes->handleClassLikeReferenceInMigration(
$codebase,
$statements_analyzer,
$stmt->class,
$fq_class_name,
$context->calling_method_id,
2019-11-17 01:59:08 +01:00
false,
$stmt->class->parts[0] === 'self'
);
}
if ($codebase->store_node_types
&& $method_id
&& !$context->collect_initializations
&& !$context->collect_mutations
) {
$codebase->analyzer->addNodeReference(
2018-11-11 18:01:14 +01:00
$statements_analyzer->getFilePath(),
$stmt->name,
$method_id . '()'
);
}
2019-02-24 07:33:25 +01:00
if ($codebase->store_node_types
&& !$context->collect_initializations
&& !$context->collect_mutations
&& ($stmt_type = $statements_analyzer->node_data->getType($stmt))
) {
$codebase->analyzer->addNodeType(
2018-11-11 18:01:14 +01:00
$statements_analyzer->getFilePath(),
$stmt->name,
2020-02-23 23:03:27 +01:00
$stmt_type->getId(),
$stmt
);
}
2018-01-29 00:29:38 +01:00
}
if ($method_id === null) {
return self::checkMethodArgs(
$method_id,
$stmt->args,
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();
}
if (!$statements_analyzer->node_data->getType($stmt)) {
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
}
2020-05-18 21:13:27 +02:00
return true;
2018-01-29 00:29:38 +01:00
}
/**
2020-02-02 00:24:09 +01:00
* @param array<int, PhpParser\Node\Arg> $args
* @return false|null
*/
private static function checkPseudoMethod(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\StaticCall $stmt,
\Psalm\Internal\MethodIdentifier $method_id,
string $fq_class_name,
array $args,
\Psalm\Storage\ClassLikeStorage $class_storage,
\Psalm\Storage\MethodStorage $pseudo_method_storage,
Context $context
) {
if (self::checkFunctionArguments(
$statements_analyzer,
$args,
$pseudo_method_storage->params,
(string) $method_id,
$context
) === false) {
return false;
}
if (self::checkFunctionLikeArgumentsMatch(
$statements_analyzer,
$args,
null,
$pseudo_method_storage->params,
$pseudo_method_storage,
null,
null,
new CodeLocation($statements_analyzer, $stmt),
$context
) === false) {
return false;
}
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(
$statements_analyzer->getCodebase(),
$return_type_candidate,
$fq_class_name,
$fq_class_name,
$class_storage->parent_class
);
$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
}