1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 12:24:49 +01:00

Detect redundant cast (#4695)

* detect redundant cast

* fix redundant cast issues

* fix redundant cast in tests
This commit is contained in:
orklah 2020-11-25 18:04:48 +01:00 committed by Daniil Gentili
parent 51fed99c5d
commit 005373bbc2
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
20 changed files with 168 additions and 37 deletions

View File

@ -105,7 +105,7 @@ class DocComment
$special[$type] = [];
}
$line_number = $line_map && isset($line_map[$full_match]) ? $line_map[$full_match] : (int)$m;
$line_number = $line_map && isset($line_map[$full_match]) ? $line_map[$full_match] : $m;
$special[$type][$line_number] = rtrim($data);
}
@ -123,7 +123,7 @@ class DocComment
$special[$type] = [];
}
$line_number = $line_map && isset($line_map[$_]) ? $line_map[$_] : (int)$m;
$line_number = $line_map && isset($line_map[$_]) ? $line_map[$_] : $m;
$special[$type][$line_number] = $data;
}

View File

@ -1502,7 +1502,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
$parent_fqcln = $this->getParentFQCLN();
if ($resolved_name === 'self' && $context->self) {
$resolved_name = (string) $context->self;
$resolved_name = $context->self;
} elseif ($resolved_name === 'parent' && $parent_fqcln) {
$resolved_name = $parent_fqcln;
}
@ -1536,7 +1536,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
$parent_fqcln = $this->getParentFQCLN();
if ($resolved_name === 'self' && $context->self) {
$resolved_name = (string) $context->self;
$resolved_name = $context->self;
} elseif ($resolved_name === 'parent' && $parent_fqcln) {
$resolved_name = $parent_fqcln;
}

View File

@ -745,7 +745,7 @@ class ArrayAssignmentAnalyzer
$object_like = new TKeyedArray(
[$key_value->value => clone $current_type],
$key_value instanceof Type\Atomic\TLiteralClassString
? [(string) $key_value->value => true]
? [$key_value->value => true]
: null
);

View File

@ -9,6 +9,8 @@ use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Issue\InvalidCast;
use Psalm\Issue\PossiblyInvalidCast;
use Psalm\Issue\RedundantCondition;
use Psalm\Issue\RedundantConditionGivenDocblockType;
use Psalm\Issue\UnrecognizedExpression;
use Psalm\IssueBuffer;
use Psalm\Type;
@ -45,6 +47,26 @@ class CastAnalyzer
$maybe_type = $statements_analyzer->node_data->getType($stmt->expr);
if ($maybe_type) {
if ($maybe_type->isInt()) {
if ($maybe_type->from_docblock) {
$issue = new RedundantConditionGivenDocblockType(
'Redundant cast to ' . $maybe_type->getKey(),
new CodeLocation($statements_analyzer->getSource(), $stmt),
$maybe_type->getKey()
);
} else {
$issue = new RedundantCondition(
'Redundant cast to ' . $maybe_type->getKey(),
new CodeLocation($statements_analyzer->getSource(), $stmt),
$maybe_type->getKey()
);
}
if (IssueBuffer::accepts($issue, $statements_analyzer->getSuppressedIssues())) {
// fall through
}
}
$maybe = $maybe_type->getAtomicTypes();
if (count($maybe) === 1 && current($maybe) instanceof Type\Atomic\TBool) {
@ -78,6 +100,28 @@ class CastAnalyzer
$maybe_type = $statements_analyzer->node_data->getType($stmt->expr);
if ($maybe_type) {
if ($maybe_type->isFloat()) {
if ($maybe_type->from_docblock) {
$issue = new RedundantConditionGivenDocblockType(
'Redundant cast to ' . $maybe_type->getKey(),
new CodeLocation($statements_analyzer->getSource(), $stmt),
$maybe_type->getKey()
);
} else {
$issue = new RedundantCondition(
'Redundant cast to ' . $maybe_type->getKey(),
new CodeLocation($statements_analyzer->getSource(), $stmt),
$maybe_type->getKey()
);
}
if (IssueBuffer::accepts($issue, $statements_analyzer->getSuppressedIssues())) {
// fall through
}
}
}
$type = Type::getFloat();
if ($statements_analyzer->data_flow_graph
@ -98,6 +142,28 @@ class CastAnalyzer
$maybe_type = $statements_analyzer->node_data->getType($stmt->expr);
if ($maybe_type) {
if ($maybe_type->isBool()) {
if ($maybe_type->from_docblock) {
$issue = new RedundantConditionGivenDocblockType(
'Redundant cast to ' . $maybe_type->getKey(),
new CodeLocation($statements_analyzer->getSource(), $stmt),
$maybe_type->getKey()
);
} else {
$issue = new RedundantCondition(
'Redundant cast to ' . $maybe_type->getKey(),
new CodeLocation($statements_analyzer->getSource(), $stmt),
$maybe_type->getKey()
);
}
if (IssueBuffer::accepts($issue, $statements_analyzer->getSuppressedIssues())) {
// fall through
}
}
}
$type = Type::getBool();
if ($statements_analyzer->data_flow_graph
@ -119,6 +185,26 @@ class CastAnalyzer
$stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr);
if ($stmt_expr_type) {
if ($stmt_expr_type->isString()) {
if ($stmt_expr_type->from_docblock) {
$issue = new RedundantConditionGivenDocblockType(
'Redundant cast to ' . $stmt_expr_type->getKey(),
new CodeLocation($statements_analyzer->getSource(), $stmt),
$stmt_expr_type->getKey()
);
} else {
$issue = new RedundantCondition(
'Redundant cast to ' . $stmt_expr_type->getKey(),
new CodeLocation($statements_analyzer->getSource(), $stmt),
$stmt_expr_type->getKey()
);
}
if (IssueBuffer::accepts($issue, $statements_analyzer->getSuppressedIssues())) {
// fall through
}
}
$stmt_type = self::castStringAttempt(
$statements_analyzer,
$context,
@ -170,6 +256,26 @@ class CastAnalyzer
$all_permissible = false;
if ($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr)) {
if ($stmt_expr_type->isArray()) {
if ($stmt_expr_type->from_docblock) {
$issue = new RedundantConditionGivenDocblockType(
'Redundant cast to ' . $stmt_expr_type->getKey(),
new CodeLocation($statements_analyzer->getSource(), $stmt),
$stmt_expr_type->getKey()
);
} else {
$issue = new RedundantCondition(
'Redundant cast to ' . $stmt_expr_type->getKey(),
new CodeLocation($statements_analyzer->getSource(), $stmt),
$stmt_expr_type->getKey()
);
}
if (IssueBuffer::accepts($issue, $statements_analyzer->getSuppressedIssues())) {
// fall through
}
}
$all_permissible = true;
foreach ($stmt_expr_type->getAtomicTypes() as $type) {

View File

@ -315,13 +315,13 @@ class Reflection
private function getReflectionParamData(\ReflectionParameter $param): FunctionLikeParameter
{
$param_type = self::getPsalmTypeFromReflectionType($param->getType());
$param_name = (string)$param->getName();
$param_name = $param->getName();
$is_optional = (bool)$param->isOptional();
$is_optional = $param->isOptional();
$parameter = new FunctionLikeParameter(
$param_name,
(bool)$param->isPassedByReference(),
$param->isPassedByReference(),
$param_type,
null,
null,

View File

@ -506,7 +506,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
throw new \InvalidArgumentException("Not a valid file URI: $uri");
}
$filepath = urldecode((string) $fragments['path']);
$filepath = urldecode($fragments['path']);
if (strpos($filepath, ':') !== false) {
if ($filepath[0] === '/') {

View File

@ -92,7 +92,7 @@ class StatementsProvider
$from_cache = false;
$version = (string) PHP_PARSER_VERSION . $this->this_modified_time;
$version = PHP_PARSER_VERSION . $this->this_modified_time;
$file_contents = $this->file_provider->getContents($file_path);
$modified_time = $this->file_provider->getModifiedTime($file_path);

View File

@ -166,8 +166,8 @@ class ObjectComparator
&& $intersection_input_type instanceof TTemplateParam
) {
if ($intersection_container_type->param_name !== $intersection_input_type->param_name
|| ((string)$intersection_container_type->defining_class
!== (string)$intersection_input_type->defining_class
|| ($intersection_container_type->defining_class
!== $intersection_input_type->defining_class
&& \substr($intersection_input_type->defining_class, 0, 3) !== 'fn-'
&& \substr($intersection_container_type->defining_class, 0, 3) !== 'fn-')
) {

View File

@ -569,7 +569,7 @@ class TypeCombiner
}
if (!$allow_mixed_union) {
return Type::getMixed((bool) $combination->mixed_from_loop_isset);
return Type::getMixed($combination->mixed_from_loop_isset);
}
}

View File

@ -891,7 +891,7 @@ class TypeParser
$offset_template_type = array_values($offset_template_data[''][0]->getAtomicTypes())[0];
if ($offset_template_type instanceof Atomic\TTemplateKeyOf) {
$offset_defining_class = (string) $offset_template_type->defining_class;
$offset_defining_class = $offset_template_type->defining_class;
}
}

View File

@ -1416,6 +1416,30 @@ class Union implements TypeNode
) === count($this->types);
}
/**
* @return bool true if this is a boolean
*/
public function isBool(): bool
{
if (!$this->isSingle()) {
return false;
}
return isset($this->types['bool']);
}
/**
* @return bool true if this is an array
*/
public function isArray(): bool
{
if (!$this->isSingle()) {
return false;
}
return isset($this->types['array']);
}
/**
* @return bool true if this is a string literal with only one possible value
*/

View File

@ -129,7 +129,7 @@ function requireAutoloaders(string $current_dir, bool $has_explicit_root, string
exit(1);
}
define('PSALM_VERSION', (string)\PackageVersions\Versions::getVersion('vimeo/psalm'));
define('PSALM_VERSION', \PackageVersions\Versions::getVersion('vimeo/psalm'));
define('PHP_PARSER_VERSION', \PackageVersions\Versions::getVersion('nikic/php-parser'));
return $first_autoloader;

View File

@ -821,7 +821,7 @@ if (!isset($options['i'])) {
$init_level = \Psalm\Config\Creator::getLevel(
array_merge(...array_values($issues_by_file)),
(int) array_sum($mixed_counts)
array_sum($mixed_counts)
);
}

View File

@ -1032,18 +1032,6 @@ class ArrayAssignmentTest extends TestCase
'$b' => 'array<empty, empty>',
],
],
'coerceListToArray' => [
'<?php
/**
* @param list<int> $_bar
*/
function foo(array $_bar) : void {}
/**
* @param list<int> $bar
*/
function baz(array $bar) : void { foo((array) $bar); }',
],
'getOnCoercedArray' => [
'<?php
function getArray() : array {
@ -1884,6 +1872,19 @@ class ArrayAssignmentTest extends TestCase
$_a = [$a => "a"];',
'error_message' => 'InvalidArrayOffset',
],
'coerceListToArray' => [
'<?php
/**
* @param list<int> $_bar
*/
function foo(array $_bar) : void {}
/**
* @param list<int> $bar
*/
function baz(array $bar) : void { foo((array) $bar); }',
'error_message' => 'RedundantCondition',
],
];
}
}

View File

@ -107,7 +107,7 @@ class ClassLoadOrderTest extends TestCase
class B extends A {
/** @return void */
public function foo() {
echo (string)(new C)->bar;
echo (new C)->bar;
}
}

View File

@ -453,7 +453,7 @@ class MethodCallTest extends TestCase
'defineVariableCreatedInArgToMixed' => [
'<?php
function bar($a) : void {
if ($a->foo($b = (int) 5)) {
if ($a->foo($b = (int) "5")) {
echo $b;
}
}',

View File

@ -496,7 +496,7 @@ class MethodSignatureTest extends TestCase
{
[
$this->id,
] = (array) \unserialize((string) $serialized);
] = (array) \unserialize($serialized);
}
public function serialize() : string

View File

@ -355,7 +355,7 @@ class AssignmentInConditionalTest extends \Psalm\Tests\TestCase
'passedByRefInIf' => [
'<?php
if (preg_match("/bad/", "badger", $matches)) {
echo (string)$matches[0];
echo $matches[0];
}',
],
'passByRefInIfCheckAfter' => [
@ -363,13 +363,13 @@ class AssignmentInConditionalTest extends \Psalm\Tests\TestCase
if (!preg_match("/bad/", "badger", $matches)) {
exit();
}
echo (string)$matches[0];',
echo $matches[0];',
],
'passByRefInIfWithBoolean' => [
'<?php
$a = (bool)rand(0, 1);
if ($a && preg_match("/bad/", "badger", $matches)) {
echo (string)$matches[0];
echo $matches[0];
}',
],
'bleedElseifAssignedVarsIntoElseScope' => [

View File

@ -40,7 +40,7 @@ class ScopeTest extends \Psalm\Tests\TestCase
'<?php
$a = preg_match("/bad/", "badger", $matches) > 0;
if ($a) {
echo (string)$matches[1];
echo $matches[1];
}',
],
'functionExists' => [

View File

@ -147,10 +147,10 @@ class UnusedVariableTest extends TestCase
],
'varRedefinedInIfWithReference' => [
'<?php
$a = (string) "fdf";
$a = (string) 5;
if (rand(0, 1)) {
$a = (string) "ard";
$a = (string) 6;
}
echo $a;',