-
Notifications
You must be signed in to change notification settings - Fork 534
Fix "offset might not exist" false-positives when offset is a expression #4372
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
reduced reproducer of the <?php declare(strict_types = 1);
namespace PHPStan\Type;
use function is_callable;
final class NameScope {}
final class FileTypeMapper
{
/** @var (true|callable(): NameScope|NameScope)[][] */
private array $inProcess = [];
public function getNameScope(
string $fileName,
?string $className,
?string $traitName,
?string $functionName,
): NameScope
{
$nameScopeKey = $this->getNameScopeKey($fileName, $className, $traitName, $functionName);
if (!isset($this->inProcess[$fileName][$nameScopeKey])) { // wrong $fileName due to traits
throw new \RuntimeException();
}
if ($this->inProcess[$fileName][$nameScopeKey] === true) { // PHPDoc has cyclic dependency
throw new \RuntimeException();
}
if (is_callable($this->inProcess[$fileName][$nameScopeKey])) {
$resolveCallback = $this->inProcess[$fileName][$nameScopeKey];
$this->inProcess[$fileName][$nameScopeKey] = true;
$this->inProcess[$fileName][$nameScopeKey] = $resolveCallback();
}
return $this->inProcess[$fileName][$nameScopeKey];
}
private function getNameScopeKey(
?string $file,
?string $class,
?string $trait,
?string $function,
): string
{
return '';
}
} leads to
|
&& !$expr->dim instanceof Expr\PreInc | ||
&& !$expr->dim instanceof Expr\PreDec | ||
&& !$expr->dim instanceof Expr\PostDec | ||
&& !$expr->dim instanceof Expr\PostInc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
required change to not regress existing tests like
<?php
$anotherIndex = 0;
$postIncArray = [];
$postIncArray[$anotherIndex++] = $anotherIndex++;
\PHPStan\Testing\assertType('array{1}', $postIncArray);
if ($this->itemType->isConstantArray()->yes() && $valueType->isConstantArray()->yes()) { | ||
$constArrays = $valueType->getConstantArrays(); | ||
$newItemType = $this->itemType; | ||
foreach($constArrays as $constArray) { | ||
foreach($constArray->getKeyTypes() as $keyType) { | ||
$newItemType = $newItemType->setExistingOffsetValueType($keyType, $constArray->getOffsetValueType($keyType)); | ||
} | ||
} | ||
|
||
if ($newItemType !== $this->itemType) { | ||
return new self( | ||
$this->keyType, | ||
$newItemType | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
required to prevent regression in
@ -0,0 +1,22 @@
<?php
namespace AssignNestedArrays;
use function PHPStan\debugScope;
use function PHPStan\Testing\assertType;
class Foo
{
public function doBar(int $i, int $j)
{
$array = [];
$array[$i][$j]['bar'] = 1;
debugScope();
$array[$i][$j]['baz'] = 2;
debugScope();
assertType('non-empty-array<int, non-empty-array<int, array{bar: 1, baz: 2}>>', $array);
}
}
ok - I think after all required PRs (see description) have been merged, this one will be ready to land |
9193974
to
dcfd808
Compare
This pull request has been marked as ready for review. |
} | ||
|
||
if ($scope->hasExpressionType($arrayDimFetch)->yes()) { // keep list for $list[$index] assignments | ||
if (!$arrayDimFetch->dim instanceof BinaryOp\Plus) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ondrejmirtes why is the issue related to Plus? there is no +
contained in the related code example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ohh.. the Plus is related to the list-inference logic below.
so it was a bug lingering for longer already :)
looking at issue bot, the last fix also fixes more stuff.. adding regression tests now.. |
src/Analyser/NodeScopeResolver.php
Outdated
/** | ||
* @param list<ArrayDimFetch> $dimFetchStack | ||
* @param list<Type|null> $offsetTypes | ||
* @param list<array{Expr, Type}> $additionalExpressions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of by-ref parameters. I'd rather if the method returned array{Type, list<array{Expr, Type}>}
😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
Awesome, thank you! |
This is really nice PR and very solid work, but I found a regression: https://phpstan.org/r/16197474-84cb-43ad-b983-ad78371fba02 My guess is that the bug will be somewhere in |
And another one: https://phpstan.org/r/80fa95b4-c7a0-4591-937d-efbf39561d95 (the nullCoalesce.offset ones) I guess it's not really easy to reason about that. After the for loop iteration, we can be sure that
We don't know whether |
my gut feeling is we miss to generalize key/value types when |
I have a different theory now and possible solution - it's a problem when merging scopes. The So I think when we're merging scopes and the type of $array changes, we need to throw away all other expressions that contain $array and not merge them. I think somewhere in MutatingScope we do or used to do in the past was to sort expressions by length so that the shorter ones are evaluated first. It might be useful in merging too. |
closes phpstan/phpstan#9494
closes phpstan/phpstan#12115
closes phpstan/phpstan#12805
closes phpstan/phpstan#12931
closes phpstan/phpstan#13214
closes phpstan/phpstan#13039
closes phpstan/phpstan#13538
requires
instanceof Type
checks #4380