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:
parent
a871d0dd58
commit
b7a68edd0b
@ -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] : [];
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user