1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Simplify complex methods

This commit is contained in:
Matt Brown 2021-03-18 15:09:03 -04:00
parent a871d0dd58
commit b7a68edd0b
2 changed files with 532 additions and 455 deletions

View File

@ -51,79 +51,13 @@ class AssertionFinder
$if_types = [];
if ($conditional instanceof PhpParser\Node\Expr\Instanceof_) {
$instanceof_types = self::getInstanceOfTypes($conditional, $this_class_name, $source);
if ($instanceof_types) {
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->expr,
$this_class_name,
$source
);
if ($var_name) {
$if_types[$var_name] = [$instanceof_types];
$var_type = $source instanceof StatementsAnalyzer
? $source->node_data->getType($conditional->expr)
: null;
foreach ($instanceof_types as $instanceof_type) {
if ($instanceof_type[0] === '=') {
$instanceof_type = substr($instanceof_type, 1);
}
if ($codebase
&& $var_type
&& $inside_negation
&& $source instanceof StatementsAnalyzer
) {
if ($codebase->interfaceExists($instanceof_type)) {
continue;
}
$instanceof_type = Type::parseString(
$instanceof_type,
null,
$source->getTemplateTypeMap() ?: []
);
if (!UnionTypeComparator::canExpressionTypesBeIdentical(
$codebase,
$instanceof_type,
$var_type
)) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new RedundantConditionGivenDocblockType(
$var_type->getId() . ' does not contain '
. $instanceof_type->getId(),
new CodeLocation($source, $conditional),
$var_type->getId() . ' ' . $instanceof_type->getId()
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new RedundantCondition(
$var_type->getId() . ' cannot be identical to '
. $instanceof_type->getId(),
new CodeLocation($source, $conditional),
$var_type->getId() . ' ' . $instanceof_type->getId()
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
}
}
return $if_types ? [$if_types] : [];
return self::getInstanceofAssertions(
$conditional,
$codebase,
$source,
$this_class_name,
$inside_negation
);
}
if ($conditional instanceof PhpParser\Node\Expr\Assign) {
@ -179,7 +113,7 @@ class AssertionFinder
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Identical ||
$conditional instanceof PhpParser\Node\Expr\BinaryOp\Equal
) {
$and_types = self::scrapeEqualityAssertions(
return self::scrapeEqualityAssertions(
$conditional,
$this_class_name,
$source,
@ -188,14 +122,12 @@ class AssertionFinder
$cache,
$inside_conditional
);
return $and_types;
}
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical ||
$conditional instanceof PhpParser\Node\Expr\BinaryOp\NotEqual
) {
$and_types = self::scrapeInequalityAssertions(
return self::scrapeInequalityAssertions(
$conditional,
$this_class_name,
$source,
@ -204,242 +136,37 @@ class AssertionFinder
$cache,
$inside_conditional
);
return $and_types;
}
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual
) {
$min_count = null;
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
$min_comparison = null;
$positive_number_position = self::hasPositiveNumberCheck($conditional, $min_comparison);
$max_count = null;
$count_inequality_position = self::hasLessThanCountEqualityCheck($conditional, $max_count);
if ($count_equality_position) {
if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
$counted_expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$count_equality_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $counted_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$counted_expr->args[0]->value,
$this_class_name,
$source
);
if ($var_name) {
if (self::hasReconcilableNonEmptyCountEqualityCheck($conditional)) {
$if_types[$var_name] = [['non-empty-countable']];
} else {
if ($min_count) {
$if_types[$var_name] = [['=has-at-least-' . $min_count]];
} else {
$if_types[$var_name] = [['=non-empty-countable']];
}
}
}
return $if_types ? [$if_types] : [];
}
if ($count_inequality_position) {
if ($count_inequality_position === self::ASSIGNMENT_TO_LEFT) {
$count_expr = $conditional->right;
} else {
throw new \UnexpectedValueException('$count_inequality_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$count_expr->args[0]->value,
$this_class_name,
$source
);
if ($var_name) {
if ($max_count) {
$if_types[$var_name] = [['!has-at-least-' . ($max_count + 1)]];
} else {
$if_types[$var_name] = [['!non-empty-countable']];
}
}
return $if_types ? [$if_types] : [];
}
if ($positive_number_position) {
if ($positive_number_position === self::ASSIGNMENT_TO_RIGHT) {
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->left,
$this_class_name,
$source
);
$value_node = $conditional->left;
} else {
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->right,
$this_class_name,
$source
);
$value_node = $conditional->right;
}
if ($codebase
&& $source instanceof StatementsAnalyzer
&& ($var_type = $source->node_data->getType($value_node))
&& $var_type->isSingle()
&& $var_type->hasBool()
&& $min_comparison > 1
) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new DocblockTypeContradiction(
$var_type . ' cannot be greater than ' . $min_comparison,
new CodeLocation($source, $conditional),
null
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new TypeDoesNotContainType(
$var_type . ' cannot be greater than ' . $min_comparison,
new CodeLocation($source, $conditional),
null
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
if ($var_name) {
$if_types[$var_name] = [[($min_comparison === 1 ? '' : '=') . 'positive-numeric']];
}
return $if_types ? [$if_types] : [];
}
return [];
return self::getGreaterAssertions(
$conditional,
$codebase,
$source,
$this_class_name
);
}
if ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller
|| $conditional instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual
) {
$min_count = null;
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
$typed_value_position = self::hasTypedValueComparison($conditional, $source);
$max_count = null;
$count_inequality_position = self::hasLessThanCountEqualityCheck($conditional, $max_count);
if ($count_equality_position) {
if ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
$count_expr = $conditional->right;
} else {
throw new \UnexpectedValueException('$count_equality_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$count_expr->args[0]->value,
$this_class_name,
$source
);
if ($var_name) {
if ($min_count) {
$if_types[$var_name] = [['=has-at-least-' . $min_count]];
} else {
$if_types[$var_name] = [['=non-empty-countable']];
}
}
return $if_types ? [$if_types] : [];
}
if ($count_inequality_position) {
if ($count_inequality_position === self::ASSIGNMENT_TO_RIGHT) {
$count_expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$count_inequality_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$count_expr->args[0]->value,
$this_class_name,
$source
);
if ($var_name) {
if ($max_count) {
$if_types[$var_name] = [['!has-at-least-' . ($max_count + 1)]];
} else {
$if_types[$var_name] = [['!non-empty-countable']];
}
}
return $if_types ? [$if_types] : [];
}
if ($typed_value_position) {
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->left,
$this_class_name,
$source
);
$expr = $conditional->right;
} elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->right,
$this_class_name,
$source
);
$expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$typed_value_position value');
}
$expr_type = $source instanceof StatementsAnalyzer
? $source->node_data->getType($expr)
: null;
if ($var_name
&& $expr_type
&& $expr_type->isSingleIntLiteral()
&& ($expr_type->getSingleIntLiteral()->value === 0)
) {
$if_types[$var_name] = [['=isset']];
}
return $if_types ? [$if_types] : [];
}
return [];
return self::getSmallerAssertions(
$conditional,
$source,
$this_class_name
);
}
if ($conditional instanceof PhpParser\Node\Expr\FuncCall) {
$and_types = self::processFunctionCall(
return self::processFunctionCall(
$conditional,
$this_class_name,
$source,
$codebase,
$inside_negation
);
return $and_types;
}
if ($conditional instanceof PhpParser\Node\Expr\MethodCall
@ -3833,4 +3560,331 @@ class AssertionFinder
return $if_types ? [$if_types] : [];
}
/**
* @param PhpParser\Node\Expr\BinaryOp\Greater|PhpParser\Node\Expr\BinaryOp\GreaterOrEqual $conditional
*
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
*/
private static function getGreaterAssertions(
PhpParser\Node\Expr $conditional,
?Codebase $codebase,
FileSource $source,
?string $this_class_name
): array {
$if_types = [];
$min_count = null;
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
$min_comparison = null;
$positive_number_position = self::hasPositiveNumberCheck($conditional, $min_comparison);
$max_count = null;
$count_inequality_position = self::hasLessThanCountEqualityCheck($conditional, $max_count);
if ($count_equality_position) {
if ($count_equality_position === self::ASSIGNMENT_TO_RIGHT) {
$counted_expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$count_equality_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $counted_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$counted_expr->args[0]->value,
$this_class_name,
$source
);
if ($var_name) {
if (self::hasReconcilableNonEmptyCountEqualityCheck($conditional)) {
$if_types[$var_name] = [['non-empty-countable']];
} else {
if ($min_count) {
$if_types[$var_name] = [['=has-at-least-' . $min_count]];
} else {
$if_types[$var_name] = [['=non-empty-countable']];
}
}
}
return $if_types ? [$if_types] : [];
}
if ($count_inequality_position) {
if ($count_inequality_position === self::ASSIGNMENT_TO_LEFT) {
$count_expr = $conditional->right;
} else {
throw new \UnexpectedValueException('$count_inequality_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$count_expr->args[0]->value,
$this_class_name,
$source
);
if ($var_name) {
if ($max_count) {
$if_types[$var_name] = [['!has-at-least-' . ($max_count + 1)]];
} else {
$if_types[$var_name] = [['!non-empty-countable']];
}
}
return $if_types ? [$if_types] : [];
}
if ($positive_number_position) {
if ($positive_number_position === self::ASSIGNMENT_TO_RIGHT) {
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->left,
$this_class_name,
$source
);
$value_node = $conditional->left;
} else {
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->right,
$this_class_name,
$source
);
$value_node = $conditional->right;
}
if ($codebase
&& $source instanceof StatementsAnalyzer
&& ($var_type = $source->node_data->getType($value_node))
&& $var_type->isSingle()
&& $var_type->hasBool()
&& $min_comparison > 1
) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new DocblockTypeContradiction(
$var_type . ' cannot be greater than ' . $min_comparison,
new CodeLocation($source, $conditional),
null
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new TypeDoesNotContainType(
$var_type . ' cannot be greater than ' . $min_comparison,
new CodeLocation($source, $conditional),
null
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
if ($var_name) {
$if_types[$var_name] = [[($min_comparison === 1 ? '' : '=') . 'positive-numeric']];
}
return $if_types ? [$if_types] : [];
}
return [];
}
/**
* @param PhpParser\Node\Expr\BinaryOp\Smaller|PhpParser\Node\Expr\BinaryOp\SmallerOrEqual $conditional
*
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
*/
private static function getSmallerAssertions(
PhpParser\Node\Expr $conditional,
FileSource $source,
?string $this_class_name
): array {
$if_types = [];
$min_count = null;
$count_equality_position = self::hasNonEmptyCountEqualityCheck($conditional, $min_count);
$typed_value_position = self::hasTypedValueComparison($conditional, $source);
$max_count = null;
$count_inequality_position = self::hasLessThanCountEqualityCheck($conditional, $max_count);
if ($count_equality_position) {
if ($count_equality_position === self::ASSIGNMENT_TO_LEFT) {
$count_expr = $conditional->right;
} else {
throw new \UnexpectedValueException('$count_equality_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$count_expr->args[0]->value,
$this_class_name,
$source
);
if ($var_name) {
if ($min_count) {
$if_types[$var_name] = [['=has-at-least-' . $min_count]];
} else {
$if_types[$var_name] = [['=non-empty-countable']];
}
}
return $if_types ? [$if_types] : [];
}
if ($count_inequality_position) {
if ($count_inequality_position === self::ASSIGNMENT_TO_RIGHT) {
$count_expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$count_inequality_position value');
}
/** @var PhpParser\Node\Expr\FuncCall $count_expr */
$var_name = ExpressionIdentifier::getArrayVarId(
$count_expr->args[0]->value,
$this_class_name,
$source
);
if ($var_name) {
if ($max_count) {
$if_types[$var_name] = [['!has-at-least-' . ($max_count + 1)]];
} else {
$if_types[$var_name] = [['!non-empty-countable']];
}
}
return $if_types ? [$if_types] : [];
}
if ($typed_value_position) {
if ($typed_value_position === self::ASSIGNMENT_TO_RIGHT) {
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->left,
$this_class_name,
$source
);
$expr = $conditional->right;
} elseif ($typed_value_position === self::ASSIGNMENT_TO_LEFT) {
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->right,
$this_class_name,
$source
);
$expr = $conditional->left;
} else {
throw new \UnexpectedValueException('$typed_value_position value');
}
$expr_type = $source instanceof StatementsAnalyzer
? $source->node_data->getType($expr)
: null;
if ($var_name
&& $expr_type
&& $expr_type->isSingleIntLiteral()
&& ($expr_type->getSingleIntLiteral()->value === 0)
) {
$if_types[$var_name] = [['=isset']];
}
return $if_types ? [$if_types] : [];
}
return [];
}
/**
* @return list<non-empty-array<string, non-empty-list<non-empty-list<string>>>>
*/
private static function getInstanceofAssertions(
PhpParser\Node\Expr\Instanceof_ $conditional,
?Codebase $codebase,
FileSource $source,
?string $this_class_name,
bool $inside_negation
): array {
$if_types = [];
$instanceof_types = self::getInstanceOfTypes($conditional, $this_class_name, $source);
if ($instanceof_types) {
$var_name = ExpressionIdentifier::getArrayVarId(
$conditional->expr,
$this_class_name,
$source
);
if ($var_name) {
$if_types[$var_name] = [$instanceof_types];
$var_type = $source instanceof StatementsAnalyzer
? $source->node_data->getType($conditional->expr)
: null;
foreach ($instanceof_types as $instanceof_type) {
if ($instanceof_type[0] === '=') {
$instanceof_type = substr($instanceof_type, 1);
}
if ($codebase
&& $var_type
&& $inside_negation
&& $source instanceof StatementsAnalyzer
) {
if ($codebase->interfaceExists($instanceof_type)) {
continue;
}
$instanceof_type = Type::parseString(
$instanceof_type,
null,
$source->getTemplateTypeMap() ?: []
);
if (!UnionTypeComparator::canExpressionTypesBeIdentical(
$codebase,
$instanceof_type,
$var_type
)) {
if ($var_type->from_docblock) {
if (IssueBuffer::accepts(
new RedundantConditionGivenDocblockType(
$var_type->getId() . ' does not contain '
. $instanceof_type->getId(),
new CodeLocation($source, $conditional),
$var_type->getId() . ' ' . $instanceof_type->getId()
),
$source->getSuppressedIssues()
)) {
// fall through
}
} else {
if (IssueBuffer::accepts(
new RedundantCondition(
$var_type->getId() . ' cannot be identical to '
. $instanceof_type->getId(),
new CodeLocation($source, $conditional),
$var_type->getId() . ' ' . $instanceof_type->getId()
),
$source->getSuppressedIssues()
)) {
// fall through
}
}
}
}
}
}
}
return $if_types ? [$if_types] : [];
}
}

View File

@ -904,168 +904,15 @@ class ArgumentAnalyzer
|| $input_expr instanceof PhpParser\Node\Expr\Array_
|| $input_expr instanceof PhpParser\Node\Expr\BinaryOp\Concat
) {
foreach ($param_type->getAtomicTypes() as $param_type_part) {
if ($param_type_part instanceof TClassString
&& $input_expr instanceof PhpParser\Node\Scalar\String_
&& $param_type->isSingle()
) {
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$statements_analyzer,
$input_expr->value,
$arg_location,
$context->self,
$context->calling_method_id,
$statements_analyzer->getSuppressedIssues()
) === false
) {
return null;
}
} elseif ($param_type_part instanceof TArray
&& $input_expr instanceof PhpParser\Node\Expr\Array_
) {
foreach ($param_type_part->type_params[1]->getAtomicTypes() as $param_array_type_part) {
if ($param_array_type_part instanceof TClassString) {
foreach ($input_expr->items as $item) {
if ($item && $item->value instanceof PhpParser\Node\Scalar\String_) {
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$statements_analyzer,
$item->value->value,
$arg_location,
$context->self,
$context->calling_method_id,
$statements_analyzer->getSuppressedIssues()
) === false
) {
return null;
}
}
}
}
}
} elseif ($param_type_part instanceof TCallable) {
$can_be_callable_like_array = false;
if ($param_type->hasArray()) {
/**
* @psalm-suppress PossiblyUndefinedStringArrayOffset
*/
$param_array_type = $param_type->getAtomicTypes()['array'];
self::verifyExplicitParam(
$statements_analyzer,
$param_type,
$arg_location,
$input_expr,
$context
);
$row_type = null;
if ($param_array_type instanceof TList) {
$row_type = $param_array_type->type_param;
} elseif ($param_array_type instanceof TArray) {
$row_type = $param_array_type->type_params[1];
} elseif ($param_array_type instanceof Type\Atomic\TKeyedArray) {
$row_type = $param_array_type->getGenericArrayType()->type_params[1];
}
if ($row_type &&
($row_type->hasMixed() || $row_type->hasString())
) {
$can_be_callable_like_array = true;
}
}
if (!$can_be_callable_like_array) {
$function_ids = CallAnalyzer::getFunctionIdsFromCallableArg(
$statements_analyzer,
$input_expr
);
foreach ($function_ids as $function_id) {
if (strpos($function_id, '::') !== false) {
if ($function_id[0] === '$') {
$function_id = \substr($function_id, 1);
}
$function_id_parts = explode('&', $function_id);
$non_existent_method_ids = [];
$has_valid_method = false;
foreach ($function_id_parts as $function_id_part) {
[$callable_fq_class_name, $method_name] = explode('::', $function_id_part);
switch ($callable_fq_class_name) {
case 'self':
case 'static':
case 'parent':
$container_class = $statements_analyzer->getFQCLN();
if ($callable_fq_class_name === 'parent') {
$container_class = $statements_analyzer->getParentFQCLN();
}
if (!$container_class) {
continue 2;
}
$callable_fq_class_name = $container_class;
}
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$statements_analyzer,
$callable_fq_class_name,
$arg_location,
$context->self,
$context->calling_method_id,
$statements_analyzer->getSuppressedIssues()
) === false
) {
return null;
}
$function_id_part = new \Psalm\Internal\MethodIdentifier(
$callable_fq_class_name,
strtolower($method_name)
);
$call_method_id = new \Psalm\Internal\MethodIdentifier(
$callable_fq_class_name,
'__call'
);
if (!$codebase->classOrInterfaceExists($callable_fq_class_name)) {
return null;
}
if (!$codebase->methods->methodExists($function_id_part)
&& !$codebase->methods->methodExists($call_method_id)
) {
$non_existent_method_ids[] = $function_id_part;
} else {
$has_valid_method = true;
}
}
if (!$has_valid_method && !$param_type->hasString() && !$param_type->hasArray()) {
if (MethodAnalyzer::checkMethodExists(
$codebase,
$non_existent_method_ids[0],
$arg_location,
$statements_analyzer->getSuppressedIssues()
) === false
) {
return null;
}
}
} else {
if (!$param_type->hasString()
&& !$param_type->hasArray()
&& CallAnalyzer::checkFunctionExists(
$statements_analyzer,
$function_id,
$arg_location,
false
) === false
) {
return null;
}
}
}
}
}
}
return null;
}
if (!$param_type->isNullable() && $cased_method_id !== 'echo' && $cased_method_id !== 'print') {
@ -1142,6 +989,182 @@ class ArgumentAnalyzer
return null;
}
/**
* @param PhpParser\Node\Scalar\String_|PhpParser\Node\Expr\Array_|PhpParser\Node\Expr\BinaryOp\Concat $input_expr
*/
private static function verifyExplicitParam(
StatementsAnalyzer $statements_analyzer,
Type\Union $param_type,
CodeLocation $arg_location,
PhpParser\Node\Expr $input_expr,
Context $context
) : void {
$codebase = $statements_analyzer->getCodebase();
foreach ($param_type->getAtomicTypes() as $param_type_part) {
if ($param_type_part instanceof TClassString
&& $input_expr instanceof PhpParser\Node\Scalar\String_
&& $param_type->isSingle()
) {
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$statements_analyzer,
$input_expr->value,
$arg_location,
$context->self,
$context->calling_method_id,
$statements_analyzer->getSuppressedIssues()
) === false
) {
return;
}
} elseif ($param_type_part instanceof TArray
&& $input_expr instanceof PhpParser\Node\Expr\Array_
) {
foreach ($param_type_part->type_params[1]->getAtomicTypes() as $param_array_type_part) {
if ($param_array_type_part instanceof TClassString) {
foreach ($input_expr->items as $item) {
if ($item && $item->value instanceof PhpParser\Node\Scalar\String_) {
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$statements_analyzer,
$item->value->value,
$arg_location,
$context->self,
$context->calling_method_id,
$statements_analyzer->getSuppressedIssues()
) === false
) {
return;
}
}
}
}
}
} elseif ($param_type_part instanceof TCallable) {
$can_be_callable_like_array = false;
if ($param_type->hasArray()) {
/**
* @psalm-suppress PossiblyUndefinedStringArrayOffset
*/
$param_array_type = $param_type->getAtomicTypes()['array'];
$row_type = null;
if ($param_array_type instanceof TList) {
$row_type = $param_array_type->type_param;
} elseif ($param_array_type instanceof TArray) {
$row_type = $param_array_type->type_params[1];
} elseif ($param_array_type instanceof Type\Atomic\TKeyedArray) {
$row_type = $param_array_type->getGenericArrayType()->type_params[1];
}
if ($row_type &&
($row_type->hasMixed() || $row_type->hasString())
) {
$can_be_callable_like_array = true;
}
}
if (!$can_be_callable_like_array) {
$function_ids = CallAnalyzer::getFunctionIdsFromCallableArg(
$statements_analyzer,
$input_expr
);
foreach ($function_ids as $function_id) {
if (strpos($function_id, '::') !== false) {
if ($function_id[0] === '$') {
$function_id = \substr($function_id, 1);
}
$function_id_parts = explode('&', $function_id);
$non_existent_method_ids = [];
$has_valid_method = false;
foreach ($function_id_parts as $function_id_part) {
[$callable_fq_class_name, $method_name] = explode('::', $function_id_part);
switch ($callable_fq_class_name) {
case 'self':
case 'static':
case 'parent':
$container_class = $statements_analyzer->getFQCLN();
if ($callable_fq_class_name === 'parent') {
$container_class = $statements_analyzer->getParentFQCLN();
}
if (!$container_class) {
continue 2;
}
$callable_fq_class_name = $container_class;
}
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
$statements_analyzer,
$callable_fq_class_name,
$arg_location,
$context->self,
$context->calling_method_id,
$statements_analyzer->getSuppressedIssues()
) === false
) {
return;
}
$function_id_part = new \Psalm\Internal\MethodIdentifier(
$callable_fq_class_name,
strtolower($method_name)
);
$call_method_id = new \Psalm\Internal\MethodIdentifier(
$callable_fq_class_name,
'__call'
);
if (!$codebase->classOrInterfaceExists($callable_fq_class_name)) {
return;
}
if (!$codebase->methods->methodExists($function_id_part)
&& !$codebase->methods->methodExists($call_method_id)
) {
$non_existent_method_ids[] = $function_id_part;
} else {
$has_valid_method = true;
}
}
if (!$has_valid_method && !$param_type->hasString() && !$param_type->hasArray()) {
if (MethodAnalyzer::checkMethodExists(
$codebase,
$non_existent_method_ids[0],
$arg_location,
$statements_analyzer->getSuppressedIssues()
) === false
) {
return;
}
}
} else {
if (!$param_type->hasString()
&& !$param_type->hasArray()
&& CallAnalyzer::checkFunctionExists(
$statements_analyzer,
$function_id,
$arg_location,
false
) === false
) {
return;
}
}
}
}
}
}
}
/**
* @param Type\Atomic\TKeyedArray|Type\Atomic\TArray|Type\Atomic\TList $unpacked_atomic_array
*/