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

Allow @psalm-ignore-falsable-return annotation

Downgrades issues around use of current(), reset(), end() etc.
This commit is contained in:
Matt Brown 2018-01-24 18:52:58 -05:00
parent f46cf729bb
commit 543872f186
15 changed files with 160 additions and 26 deletions

View File

@ -263,6 +263,7 @@ class CommentChecker
$info->variadic = isset($comments['specials']['psalm-variadic']);
$info->ignore_nullable_return = isset($comments['specials']['psalm-ignore-nullable-return']);
$info->ignore_falsable_return = isset($comments['specials']['psalm-ignore-falsable-return']);
return $info;
}

View File

@ -543,6 +543,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
$this->function->stmts,
$closure_yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues,
true
);
@ -1187,6 +1188,7 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
$function_stmts,
$inferred_yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues,
true
);
@ -1439,7 +1441,8 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
}
}
if ($inferred_return_type->isFalsable()
if (!$ignore_falsable_issues
&& $inferred_return_type->isFalsable()
&& !$declared_return_type->isFalsable()
&& !$declared_return_type->hasBool()
) {

View File

@ -468,7 +468,10 @@ class PropertyAssignmentChecker
}
}
if ($assignment_value_type->isFalsable() && !$class_property_type->hasBool()) {
if (!$assignment_value_type->ignore_falsable_issues
&& $assignment_value_type->isFalsable()
&& !$class_property_type->hasBool()
) {
if (IssueBuffer::accepts(
new PossiblyFalsePropertyAssignmentValue(
$var_id . ' with non-falsable declared type \'' . $class_property_type .

View File

@ -170,7 +170,8 @@ class AssignmentChecker
$statements_checker->getFileChecker()->project_checker,
$assign_value_type,
$context->byref_constraints[$var_id]->type,
$assign_value_type->ignore_nullable_issues
$assign_value_type->ignore_nullable_issues,
$assign_value_type->ignore_falsable_issues
)
) {
if (IssueBuffer::accepts(

View File

@ -750,10 +750,10 @@ class CallChecker
return null;
}
if ($class_type &&
is_string($stmt->name) &&
$class_type->isNullable() &&
!$class_type->ignore_nullable_issues
if ($class_type
&& is_string($stmt->name)
&& $class_type->isNullable()
&& !$class_type->ignore_nullable_issues
) {
if (IssueBuffer::accepts(
new PossiblyNullReference(
@ -766,9 +766,10 @@ class CallChecker
}
}
if ($class_type &&
is_string($stmt->name) &&
$class_type->isFalsable()
if ($class_type
&& is_string($stmt->name)
&& $class_type->isFalsable()
&& !$class_type->ignore_falsable_issues
) {
if (IssueBuffer::accepts(
new PossiblyFalseReference(
@ -2562,7 +2563,7 @@ class CallChecker
}
}
if ($input_type->isFalsable() && !$param_type->hasBool()) {
if ($input_type->isFalsable() && !$param_type->hasBool() && !$input_type->ignore_falsable_issues) {
if (IssueBuffer::accepts(
new PossiblyFalseArgument(
'Argument ' . ($argument_offset + 1) . $method_identifier . ' cannot be false, possibly ' .

View File

@ -712,6 +712,7 @@ class ExpressionChecker
$fleshed_out_type->from_docblock = $return_type->from_docblock;
$fleshed_out_type->ignore_nullable_issues = $return_type->ignore_nullable_issues;
$fleshed_out_type->ignore_falsable_issues = $return_type->ignore_falsable_issues;
$fleshed_out_type->by_ref = $return_type->by_ref;
return $fleshed_out_type;

View File

@ -170,7 +170,8 @@ class ReturnChecker
}
}
if ($inferred_type->isFalsable()
if (!$stmt->inferredType->ignore_falsable_issues
&& $inferred_type->isFalsable()
&& !$local_return_type->isFalsable()
&& !$local_return_type->hasBool()
) {

View File

@ -292,7 +292,7 @@ class TypeChecker
$input_param,
$container_param,
$input_param->ignore_nullable_issues,
false,
$input_param->ignore_falsable_issues,
$has_scalar_match,
$type_coerced,
$type_coerced_from_mixed
@ -370,7 +370,7 @@ class TypeChecker
$input_property_type,
$container_property_type,
$input_property_type->ignore_nullable_issues,
false,
$input_property_type->ignore_falsable_issues,
$has_scalar_match,
$type_coerced,
$type_coerced_from_mixed
@ -800,6 +800,7 @@ class TypeChecker
$from_docblock = $union->from_docblock;
$ignore_nullable_issues = $union->ignore_nullable_issues;
$ignore_falsable_issues = $union->ignore_falsable_issues;
$unique_types = [];
@ -857,6 +858,7 @@ class TypeChecker
$unique_type->from_docblock = $from_docblock;
$unique_type->ignore_nullable_issues = $ignore_nullable_issues;
$unique_type->ignore_falsable_issues = $ignore_falsable_issues;
return $unique_type;
}

View File

@ -15,6 +15,7 @@ class EffectsAnalyser
* @param array<PhpParser\Node> $stmts
* @param array<int,Type\Atomic> $yield_types
* @param bool $ignore_nullable_issues
* @param bool $ignore_falsable_issues
* @param bool $collapse_types
*
* @return array<int,Type\Atomic> a list of return types
@ -23,6 +24,7 @@ class EffectsAnalyser
array $stmts,
array &$yield_types,
&$ignore_nullable_issues = false,
&$ignore_falsable_issues = false,
$collapse_types = false
) {
$return_types = [];
@ -47,6 +49,10 @@ class EffectsAnalyser
if ($stmt->inferredType->ignore_nullable_issues) {
$ignore_nullable_issues = true;
}
if ($stmt->inferredType->ignore_falsable_issues) {
$ignore_falsable_issues = true;
}
} else {
$return_types[] = new Atomic\TMixed();
}
@ -56,71 +62,131 @@ class EffectsAnalyser
} elseif ($stmt instanceof PhpParser\Node\Expr\Assign) {
$return_types = array_merge(
$return_types,
self::getReturnTypes([$stmt->expr], $yield_types, $ignore_nullable_issues)
self::getReturnTypes(
[$stmt->expr],
$yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues
)
);
} elseif ($stmt instanceof PhpParser\Node\Stmt\If_) {
$return_types = array_merge(
$return_types,
self::getReturnTypes($stmt->stmts, $yield_types, $ignore_nullable_issues)
self::getReturnTypes(
$stmt->stmts,
$yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues
)
);
foreach ($stmt->elseifs as $elseif) {
$return_types = array_merge(
$return_types,
self::getReturnTypes($elseif->stmts, $yield_types, $ignore_nullable_issues)
self::getReturnTypes(
$elseif->stmts,
$yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues
)
);
}
if ($stmt->else) {
$return_types = array_merge(
$return_types,
self::getReturnTypes($stmt->else->stmts, $yield_types, $ignore_nullable_issues)
self::getReturnTypes(
$stmt->else->stmts,
$yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues
)
);
}
} elseif ($stmt instanceof PhpParser\Node\Stmt\TryCatch) {
$return_types = array_merge(
$return_types,
self::getReturnTypes($stmt->stmts, $yield_types, $ignore_nullable_issues)
self::getReturnTypes(
$stmt->stmts,
$yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues
)
);
foreach ($stmt->catches as $catch) {
$return_types = array_merge(
$return_types,
self::getReturnTypes($catch->stmts, $yield_types, $ignore_nullable_issues)
self::getReturnTypes(
$catch->stmts,
$yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues
)
);
}
if ($stmt->finally) {
$return_types = array_merge(
$return_types,
self::getReturnTypes($stmt->finally->stmts, $yield_types, $ignore_nullable_issues)
self::getReturnTypes(
$stmt->finally->stmts,
$yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues
)
);
}
} elseif ($stmt instanceof PhpParser\Node\Stmt\For_) {
$return_types = array_merge(
$return_types,
self::getReturnTypes($stmt->stmts, $yield_types, $ignore_nullable_issues)
self::getReturnTypes(
$stmt->stmts,
$yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues
)
);
} elseif ($stmt instanceof PhpParser\Node\Stmt\Foreach_) {
$return_types = array_merge(
$return_types,
self::getReturnTypes($stmt->stmts, $yield_types, $ignore_nullable_issues)
self::getReturnTypes(
$stmt->stmts,
$yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues
)
);
} elseif ($stmt instanceof PhpParser\Node\Stmt\While_) {
$return_types = array_merge(
$return_types,
self::getReturnTypes($stmt->stmts, $yield_types, $ignore_nullable_issues)
self::getReturnTypes(
$stmt->stmts,
$yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues
)
);
} elseif ($stmt instanceof PhpParser\Node\Stmt\Do_) {
$return_types = array_merge(
$return_types,
self::getReturnTypes($stmt->stmts, $yield_types, $ignore_nullable_issues)
self::getReturnTypes(
$stmt->stmts,
$yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues
)
);
} elseif ($stmt instanceof PhpParser\Node\Stmt\Switch_) {
foreach ($stmt->cases as $case) {
$return_types = array_merge(
$return_types,
self::getReturnTypes($case->stmts, $yield_types, $ignore_nullable_issues)
self::getReturnTypes(
$case->stmts,
$yield_types,
$ignore_nullable_issues,
$ignore_falsable_issues
)
);
}
}

View File

@ -34,6 +34,13 @@ class FunctionDocblockComment
*/
public $ignore_nullable_return = false;
/**
* Whether or not to ignore the nullability of this function's return type
*
* @var bool
*/
public $ignore_falsable_return = false;
/**
* @var array<int, string>
*/

View File

@ -119,6 +119,7 @@ function array_reverse(array $arr, bool $preserve_keys = false) {}
*
* @param array<TKey, TValue> $arr
* @return TValue|false
* @psalm-ignore-falsable-return
*/
function current(array $arr) {}
@ -128,6 +129,7 @@ function current(array $arr) {}
*
* @param array<TKey, TValue> $arr
* @return TValue|false
* @psalm-ignore-falsable-return
*/
function reset(array &$arr) {}
@ -137,6 +139,7 @@ function reset(array &$arr) {}
*
* @param array<TKey, TValue> $arr
* @return TValue|false
* @psalm-ignore-falsable-return
*/
function end(array &$arr) {}

View File

@ -533,6 +533,10 @@ abstract class Type
$combined_type->ignore_nullable_issues = true;
}
if ($type_1->ignore_falsable_issues || $type_2->ignore_falsable_issues) {
$combined_type->ignore_falsable_issues = true;
}
if ($both_failed_reconciliation) {
$combined_type->failed_reconciliation = true;
}

View File

@ -46,6 +46,13 @@ class Union
*/
public $ignore_nullable_issues = false;
/**
* Whether or not to ignore issues with possibly-false values
*
* @var bool
*/
public $ignore_falsable_issues = false;
/**
* Whether or not the type was passed by reference
*
@ -447,6 +454,10 @@ class Union
$this->ignore_nullable_issues = true;
}
if ($new_type && $new_type->ignore_falsable_issues) {
$this->ignore_falsable_issues = true;
}
foreach ($old_type->types as $old_type_part) {
$this->removeType($old_type_part->getKey());
}

View File

@ -804,6 +804,10 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$storage->return_type->ignore_nullable_issues = true;
}
if ($docblock_info->ignore_falsable_return && $storage->return_type) {
$storage->return_type->ignore_falsable_issues = true;
}
$storage->suppressed_issues = $docblock_info->suppress;
if (!$this->config->use_docblock_types) {
@ -909,6 +913,10 @@ class DependencyFinderVisitor extends PhpParser\NodeVisitorAbstract implements P
$storage->return_type->ignore_nullable_issues = true;
}
if ($storage->return_type && $docblock_info->ignore_falsable_return) {
$storage->return_type->ignore_falsable_issues = true;
}
if ($stmt->returnsByRef() && $storage->return_type) {
$storage->return_type->by_ref = true;
}

View File

@ -417,6 +417,28 @@ class FunctionCallTest extends TestCase
'$foo' => 'array<string, Closure>',
],
],
'ignoreFalsableCurrent' => [
'<?php
/** @param string[] $arr */
function foo(array $arr): string {
return current($arr);
}
/** @param string[] $arr */
function bar(array $arr): string {
$a = current($arr);
if ($a === false) {
return "hello";
}
return $a;
}
/**
* @param string[] $arr
* @return string|false
*/
function bat(array $arr) {
return current($arr);
}',
],
];
}