mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Added support of asserting properties of objects out of scope
This commit is contained in:
parent
f9f9167d74
commit
b664850cdc
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<files psalm-version="dev-master@f62c76a334982eae94741b966ea886f0b15e4293">
|
||||
<files psalm-version="dev-master@50ec62ffd8a4b46d48835abb007d4bdd4da6c4c9">
|
||||
<file src="examples/TemplateChecker.php">
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="2">
|
||||
<code>$comment_block->tags['variablesfrom'][0]</code>
|
||||
@ -88,7 +88,8 @@
|
||||
</InvalidPropertyAssignmentValue>
|
||||
</file>
|
||||
<file src="src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php">
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="33">
|
||||
<PossiblyUndefinedIntArrayOffset occurrences="34">
|
||||
<code>$assertion->rule[0]</code>
|
||||
<code>$assertion->rule[0]</code>
|
||||
<code>$assertion->rule[0]</code>
|
||||
<code>$assertion->rule[0]</code>
|
||||
|
@ -19,8 +19,11 @@ use Psalm\FileSource;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\ClassLikeNameOptions;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Provider\ClassLikeStorageProvider;
|
||||
use Psalm\Internal\Provider\NodeDataProvider;
|
||||
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
|
||||
use Psalm\Issue\DocblockTypeContradiction;
|
||||
use Psalm\Issue\InvalidDocblock;
|
||||
use Psalm\Issue\RedundantCondition;
|
||||
use Psalm\Issue\RedundantConditionGivenDocblockType;
|
||||
use Psalm\Issue\RedundantIdentityWithTrue;
|
||||
@ -28,14 +31,21 @@ use Psalm\Issue\TypeDoesNotContainNull;
|
||||
use Psalm\Issue\TypeDoesNotContainType;
|
||||
use Psalm\Issue\UnevaluatedCode;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Storage\PropertyStorage;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use UnexpectedValueException;
|
||||
|
||||
use function array_key_exists;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function in_array;
|
||||
use function is_callable;
|
||||
use function is_int;
|
||||
use function is_numeric;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function substr;
|
||||
@ -943,7 +953,17 @@ class AssertionFinder
|
||||
if ($var_name) {
|
||||
$if_types[$var_name] = [[$assertion->rule[0][0]]];
|
||||
}
|
||||
} elseif ($assertion->var_id === '$this' && $expr instanceof PhpParser\Node\Expr\MethodCall) {
|
||||
} elseif ($assertion->var_id === '$this') {
|
||||
if (!$expr instanceof PhpParser\Node\Expr\MethodCall) {
|
||||
IssueBuffer::add(
|
||||
new InvalidDocblock(
|
||||
'Assertion of $this can be done only on method of a class',
|
||||
new CodeLocation($source, $expr)
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$var_id = ExpressionIdentifier::getArrayVarId(
|
||||
$expr->var,
|
||||
$this_class_name,
|
||||
@ -953,17 +973,74 @@ class AssertionFinder
|
||||
if ($var_id) {
|
||||
$if_types[$var_id] = [[$assertion->rule[0][0]]];
|
||||
}
|
||||
} elseif (\is_string($assertion->var_id)
|
||||
&& (
|
||||
$expr instanceof PhpParser\Node\Expr\MethodCall
|
||||
|| $expr instanceof PhpParser\Node\Expr\StaticCall
|
||||
)
|
||||
) {
|
||||
$var_id = $assertion->var_id;
|
||||
if (strpos($var_id, 'self::') === 0) {
|
||||
$var_id = $this_class_name . '::' . substr($var_id, 6);
|
||||
} elseif (\is_string($assertion->var_id)) {
|
||||
$is_function = substr($assertion->var_id, -2) === '()';
|
||||
$exploded_id = explode('->', $assertion->var_id);
|
||||
$var_id = $exploded_id[0] ?? null;
|
||||
$property = $exploded_id[1] ?? null;
|
||||
|
||||
if (is_numeric($var_id) && null !== $property && !$is_function) {
|
||||
$args = $expr->getArgs();
|
||||
|
||||
if (!array_key_exists($var_id, $args)) {
|
||||
IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
'Variable '.$var_id.' is not an argument so cannot be asserted',
|
||||
new CodeLocation($source, $expr)
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$arg_value = $args[$var_id]->value;
|
||||
assert($arg_value instanceof PhpParser\Node\Expr\Variable);
|
||||
|
||||
$arg_var_id = ExpressionIdentifier::getArrayVarId($arg_value, null, $source);
|
||||
|
||||
if (null === $arg_var_id) {
|
||||
IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
'Variable being asserted as argument ' . ($var_id+1) . ' cannot be found
|
||||
in local scope',
|
||||
new CodeLocation($source, $expr)
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count($exploded_id) === 2) {
|
||||
$failedMessage = self::isPropertyImmutableOnArgument(
|
||||
$property,
|
||||
$source->getNodeTypeProvider(),
|
||||
$source->getCodebase()->classlike_storage_provider,
|
||||
$arg_value
|
||||
);
|
||||
|
||||
if (null !== $failedMessage) {
|
||||
IssueBuffer::accepts(
|
||||
new InvalidDocblock($failedMessage, new CodeLocation($source, $expr))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$assertion_var_id = str_replace($var_id, $arg_var_id, $assertion->var_id);
|
||||
} elseif (!$expr instanceof PhpParser\Node\Expr\FuncCall) {
|
||||
$assertion_var_id = $assertion->var_id;
|
||||
|
||||
if (strpos($assertion_var_id, 'self::') === 0) {
|
||||
$assertion_var_id = $this_class_name.'::'.substr($assertion_var_id, 6);
|
||||
}
|
||||
} else {
|
||||
IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
sprintf('Assertion of variable "%s" cannot be recognized', $assertion->var_id),
|
||||
new CodeLocation($source, $expr)
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$if_types[$var_id] = [[$assertion->rule[0][0]]];
|
||||
$if_types[$assertion_var_id] = [[$assertion->rule[0][0]]];
|
||||
}
|
||||
|
||||
if ($if_types) {
|
||||
@ -1030,17 +1107,78 @@ class AssertionFinder
|
||||
$if_types[$var_id] = [['!' . $assertion->rule[0][0]]];
|
||||
}
|
||||
}
|
||||
} elseif (\is_string($assertion->var_id)
|
||||
&& (
|
||||
$expr instanceof PhpParser\Node\Expr\MethodCall
|
||||
|| $expr instanceof PhpParser\Node\Expr\StaticCall
|
||||
)
|
||||
) {
|
||||
$var_id = $assertion->var_id;
|
||||
if (strpos($var_id, 'self::') === 0) {
|
||||
$var_id = $this_class_name . '::' . substr($var_id, 6);
|
||||
} elseif (\is_string($assertion->var_id)) {
|
||||
$is_function = substr($assertion->var_id, -2) === '()';
|
||||
$exploded_id = explode('->', $assertion->var_id);
|
||||
$var_id = $exploded_id[0] ?? null;
|
||||
$property = $exploded_id[1] ?? null;
|
||||
|
||||
if (is_numeric($var_id) && null !== $property && !$is_function) {
|
||||
$args = $expr->getArgs();
|
||||
|
||||
if (!array_key_exists($var_id, $args)) {
|
||||
IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
'Variable '.$var_id.' is not an argument so cannot be asserted',
|
||||
new CodeLocation($source, $expr)
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
/** @var PhpParser\Node\Expr\Variable $arg_value */
|
||||
$arg_value = $args[$var_id]->value;
|
||||
|
||||
$arg_var_id = ExpressionIdentifier::getArrayVarId($arg_value, null, $source);
|
||||
|
||||
if (null === $arg_var_id) {
|
||||
IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
'Variable being asserted as argument ' . ($var_id+1) . ' cannot be found
|
||||
in local scope',
|
||||
new CodeLocation($source, $expr)
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count($exploded_id) === 2) {
|
||||
$failedMessage = self::isPropertyImmutableOnArgument(
|
||||
$property,
|
||||
$source->getNodeTypeProvider(),
|
||||
$source->getCodebase()->classlike_storage_provider,
|
||||
$arg_value
|
||||
);
|
||||
|
||||
if (null !== $failedMessage) {
|
||||
IssueBuffer::accepts(
|
||||
new InvalidDocblock($failedMessage, new CodeLocation($source, $expr))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ('!' === $assertion->rule[0][0][0]) {
|
||||
$rule = substr($assertion->rule[0][0], 1);
|
||||
} else {
|
||||
$rule = '!' . $assertion->rule[0][0];
|
||||
}
|
||||
$assertion_var_id = str_replace($var_id, $arg_var_id, $assertion->var_id);
|
||||
|
||||
$if_types[$assertion_var_id] = [[$rule]];
|
||||
} elseif (!$expr instanceof PhpParser\Node\Expr\FuncCall) {
|
||||
$var_id = $assertion->var_id;
|
||||
if (strpos($var_id, 'self::') === 0) {
|
||||
$var_id = $this_class_name.'::'.substr($var_id, 6);
|
||||
}
|
||||
$if_types[$var_id] = [['!'.$assertion->rule[0][0]]];
|
||||
} else {
|
||||
IssueBuffer::accepts(
|
||||
new InvalidDocblock(
|
||||
sprintf('Assertion of variable "%s" cannot be recognized', $assertion->var_id),
|
||||
new CodeLocation($source, $expr)
|
||||
)
|
||||
);
|
||||
}
|
||||
$if_types[$var_id] = [['!' . $assertion->rule[0][0]]];
|
||||
}
|
||||
|
||||
if ($if_types) {
|
||||
@ -3978,4 +4116,46 @@ class AssertionFinder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function isPropertyImmutableOnArgument(
|
||||
string $property,
|
||||
NodeDataProvider $node_provider,
|
||||
ClassLikeStorageProvider $class_provider,
|
||||
PhpParser\Node\Expr\Variable $arg_expr
|
||||
): ?string {
|
||||
$type = $node_provider->getType($arg_expr);
|
||||
/** @var string $name */
|
||||
$name = $arg_expr->name;
|
||||
|
||||
if (null === $type) {
|
||||
return 'Cannot resolve a type of variable ' . $name;
|
||||
}
|
||||
|
||||
foreach ($type->getAtomicTypes() as $type) {
|
||||
if (!$type instanceof TNamedObject) {
|
||||
return 'Variable ' . $name . ' is not an object so assertion cannot be applied';
|
||||
}
|
||||
|
||||
$class_definition = $class_provider->get($type->value);
|
||||
$property_definition = $class_definition->properties[$property] ?? null;
|
||||
|
||||
if (!$property_definition instanceof PropertyStorage) {
|
||||
return sprintf(
|
||||
'Property %s is not defined on variable %s so assertion cannot be applied',
|
||||
$property,
|
||||
$name
|
||||
);
|
||||
}
|
||||
|
||||
if (!$property_definition->readonly) {
|
||||
return sprintf(
|
||||
'Property %s of variable %s is not read-only/immutable so assertion cannot be applied',
|
||||
$property,
|
||||
$name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ use Psalm\Internal\Type\TemplateResult;
|
||||
use Psalm\Internal\Type\TemplateStandinTypeReplacer;
|
||||
use Psalm\Issue\ArgumentTypeCoercion;
|
||||
use Psalm\Issue\InvalidArgument;
|
||||
use Psalm\Issue\InvalidDocblock;
|
||||
use Psalm\Issue\InvalidScalarArgument;
|
||||
use Psalm\Issue\MixedArgumentTypeCoercion;
|
||||
use Psalm\Issue\UndefinedFunction;
|
||||
@ -30,9 +31,11 @@ use function array_map;
|
||||
use function array_merge;
|
||||
use function array_unique;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function is_int;
|
||||
use function is_numeric;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function str_replace;
|
||||
@ -617,7 +620,6 @@ class CallAnalyzer
|
||||
/**
|
||||
* @param PhpParser\Node\Identifier|PhpParser\Node\Name $expr
|
||||
* @param \Psalm\Storage\Assertion[] $assertions
|
||||
* @param string $thisName
|
||||
* @param list<PhpParser\Node\Arg> $args
|
||||
* @param array<string, array<string, non-empty-list<TemplateBound>>> $inferred_lower_bounds,
|
||||
*
|
||||
@ -663,6 +665,65 @@ class CallAnalyzer
|
||||
$assertion_var_id = $assertion->var_id;
|
||||
} elseif (isset($context->vars_in_scope[$assertion->var_id])) {
|
||||
$assertion_var_id = $assertion->var_id;
|
||||
} elseif (strpos($assertion->var_id, '->') !== false) {
|
||||
$exploded = explode('->', $assertion->var_id);
|
||||
|
||||
if (count($exploded) < 2) {
|
||||
IssueBuffer::add(
|
||||
new InvalidDocblock(
|
||||
'Assert notation is malformed',
|
||||
new CodeLocation($statements_analyzer, $expr)
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
[$var_id, $property] = $exploded;
|
||||
|
||||
$var_id = is_numeric($var_id) ? (int) $var_id : $var_id;
|
||||
|
||||
if (!is_int($var_id) || !isset($args[$var_id])) {
|
||||
IssueBuffer::add(
|
||||
new InvalidDocblock(
|
||||
'Variable ' . $var_id . ' is not an argument so cannot be asserted',
|
||||
new CodeLocation($statements_analyzer, $expr)
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var PhpParser\Node\Expr\Variable $arg_value */
|
||||
$arg_value = $args[$var_id]->value;
|
||||
|
||||
$arg_var_id = ExpressionIdentifier::getArrayVarId($arg_value, null, $statements_analyzer);
|
||||
|
||||
if (!$arg_var_id) {
|
||||
IssueBuffer::add(
|
||||
new InvalidDocblock(
|
||||
'Variable being asserted as argument ' . ($var_id+1) . ' cannot be found in local scope',
|
||||
new CodeLocation($statements_analyzer, $expr)
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count($exploded) === 2) {
|
||||
$failedMessage = AssertionFinder::isPropertyImmutableOnArgument(
|
||||
$property,
|
||||
$statements_analyzer->getNodeTypeProvider(),
|
||||
$statements_analyzer->getCodebase()->classlike_storage_provider,
|
||||
$arg_value
|
||||
);
|
||||
|
||||
if (null !== $failedMessage) {
|
||||
IssueBuffer::add(
|
||||
new InvalidDocblock($failedMessage, new CodeLocation($statements_analyzer, $expr))
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$assertion_var_id = str_replace((string) $var_id, $arg_var_id, $assertion->var_id);
|
||||
}
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
@ -31,6 +31,7 @@ use function explode;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function preg_split;
|
||||
use function str_replace;
|
||||
use function strlen;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
@ -1136,6 +1137,14 @@ class FunctionLikeDocblockScanner
|
||||
);
|
||||
continue 2;
|
||||
}
|
||||
|
||||
if (strpos($assertion['param_name'], $param->name.'->') === 0) {
|
||||
$storage->assertions[] = new \Psalm\Storage\Assertion(
|
||||
str_replace($param->name, (string) $i, $assertion['param_name']),
|
||||
[$assertion_type_parts]
|
||||
);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$storage->assertions[] = new \Psalm\Storage\Assertion(
|
||||
@ -1175,6 +1184,14 @@ class FunctionLikeDocblockScanner
|
||||
);
|
||||
continue 2;
|
||||
}
|
||||
|
||||
if (strpos($assertion['param_name'], $param->name.'->') === 0) {
|
||||
$storage->if_true_assertions[] = new \Psalm\Storage\Assertion(
|
||||
str_replace($param->name, (string) $i, $assertion['param_name']),
|
||||
[$assertion_type_parts]
|
||||
);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$storage->if_true_assertions[] = new \Psalm\Storage\Assertion(
|
||||
@ -1214,6 +1231,14 @@ class FunctionLikeDocblockScanner
|
||||
);
|
||||
continue 2;
|
||||
}
|
||||
|
||||
if (strpos($assertion['param_name'], $param->name.'->') === 0) {
|
||||
$storage->if_false_assertions[] = new \Psalm\Storage\Assertion(
|
||||
str_replace($param->name, (string) $i, $assertion['param_name']),
|
||||
[$assertion_type_parts]
|
||||
);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$storage->if_false_assertions[] = new \Psalm\Storage\Assertion(
|
||||
|
@ -1533,6 +1533,127 @@ class AssertAnnotationTest extends TestCase
|
||||
[],
|
||||
'7.4'
|
||||
],
|
||||
'onPropertyOfImmutableArgument' => [
|
||||
'<?php
|
||||
/** @psalm-immutable */
|
||||
class Aclass {
|
||||
public ?string $b;
|
||||
public function __construct(?string $b) {
|
||||
$this->b = $b;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-assert !null $item->b */
|
||||
function c(\Aclass $item): void {
|
||||
if (null === $item->b) {
|
||||
throw new \InvalidArgumentException("");
|
||||
}
|
||||
}
|
||||
|
||||
/** @var \Aclass $a */
|
||||
c($a);
|
||||
echo strlen($a->b);',
|
||||
],
|
||||
'inTrueOnPropertyOfImmutableArgument' => [
|
||||
'<?php
|
||||
/** @psalm-immutable */
|
||||
class A {
|
||||
public ?int $b;
|
||||
public function __construct(?int $b) {
|
||||
$this->b = $b;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-assert-if-true !null $item->b */
|
||||
function c(A $item): bool {
|
||||
return null !== $item->b;
|
||||
}
|
||||
|
||||
function check(int $a): void {}
|
||||
|
||||
/** @var A $a */
|
||||
|
||||
if (c($a)) {
|
||||
check($a->b);
|
||||
}',
|
||||
],
|
||||
'inFalseOnPropertyOfAImmutableArgument' => [
|
||||
'<?php
|
||||
/** @psalm-immutable */
|
||||
class A {
|
||||
public ?int $b;
|
||||
public function __construct(?int $b) {
|
||||
$this->b = $b;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-assert-if-false !null $item->b */
|
||||
function c(A $item): bool {
|
||||
return null === $item->b;
|
||||
}
|
||||
|
||||
function check(int $a): void {}
|
||||
|
||||
/** @var A $a */
|
||||
|
||||
if (!c($a)) {
|
||||
check($a->b);
|
||||
}',
|
||||
],
|
||||
'ifTrueOnNestedPropertyOfArgument' => [
|
||||
'<?php
|
||||
class B {
|
||||
public ?string $c;
|
||||
public function __construct(?string $c) {
|
||||
$this->c = $c;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-immutable */
|
||||
class Aclass {
|
||||
public B $b;
|
||||
public function __construct(B $b) {
|
||||
$this->b = $b;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-assert-if-true !null $item->b->c */
|
||||
function c(\Aclass $item): bool {
|
||||
return null !== $item->b->c;
|
||||
}
|
||||
|
||||
$a = new \Aclass(new \B(null));
|
||||
if (c($a)) {
|
||||
echo strlen($a->b->c);
|
||||
}',
|
||||
],
|
||||
'ifFalseOnNestedPropertyOfArgument' => [
|
||||
'<?php
|
||||
class B {
|
||||
public ?string $c;
|
||||
public function __construct(?string $c) {
|
||||
$this->c = $c;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-immutable */
|
||||
class Aclass {
|
||||
public B $b;
|
||||
public function __construct(B $b) {
|
||||
$this->b = $b;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-assert-if-false !null $item->b->c */
|
||||
function c(\Aclass $item): bool {
|
||||
return null !== $item->b->c;
|
||||
}
|
||||
|
||||
$a = new \Aclass(new \B(null));
|
||||
if (!c($a)) {
|
||||
echo strlen($a->b->c);
|
||||
}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -1798,6 +1919,67 @@ class AssertAnnotationTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'PossiblyNullReference',
|
||||
],
|
||||
'onPropertyOfMutableArgument' => [
|
||||
'<?php
|
||||
class Aclass {
|
||||
public ?string $b;
|
||||
public function __construct(?string $b) {
|
||||
$this->b = $b;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-assert !null $item->b */
|
||||
function c(\Aclass $item): void {
|
||||
if (null === $item->b) {
|
||||
throw new \InvalidArgumentException("");
|
||||
}
|
||||
}
|
||||
|
||||
/** @var \Aclass $a */
|
||||
c($a);
|
||||
echo strlen($a->b);',
|
||||
'error_message' => 'InvalidDocblock',
|
||||
],
|
||||
'ifTrueOnPropertyOfMutableArgument' => [
|
||||
'<?php
|
||||
class Aclass {
|
||||
public ?string $b;
|
||||
public function __construct(?string $b) {
|
||||
$this->b = $b;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-assert-if-true !null $item->b */
|
||||
function c(\Aclass $item): bool {
|
||||
return null !== $item->b;
|
||||
}
|
||||
|
||||
/** @var \Aclass $a */
|
||||
if (c($a)) {
|
||||
echo strlen($a->b);
|
||||
}',
|
||||
'error_message' => 'InvalidDocblock',
|
||||
],
|
||||
'ifFalseOnPropertyOfMutableArgument' => [
|
||||
'<?php
|
||||
class Aclass {
|
||||
public ?string $b;
|
||||
public function __construct(?string $b) {
|
||||
$this->b = $b;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-assert-if-false !null $item->b */
|
||||
function c(\Aclass $item): bool {
|
||||
return null === $item->b;
|
||||
}
|
||||
|
||||
/** @var \Aclass $a */
|
||||
if (!c($a)) {
|
||||
echo strlen($a->b);
|
||||
}',
|
||||
'error_message' => 'InvalidDocblock',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user