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:
parent
f46cf729bb
commit
543872f186
@ -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;
|
||||
}
|
||||
|
@ -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()
|
||||
) {
|
||||
|
@ -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 .
|
||||
|
@ -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(
|
||||
|
@ -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 ' .
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
*/
|
||||
|
@ -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) {}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user