-
Notifications
You must be signed in to change notification settings - Fork 539
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. |
I'm trying to understand the problem in https://phpstan.org/r/80fa95b4-c7a0-4591-937d-efbf39561d95 and I think I got it, but I don't know how to implement the fix. The problem goes away when I make I think the problem is that after Because we can't be sure about that. Sure, if But if So there's a wrong assumption that when writing to Does this give you enough info to fix it? :) |
The type of Again, some piece of code needs to account for whether |
thanks for the investigation. I compared it with the types before this commit and they were already wrong (same results). just to be sure, we expect the following types, right? - assertType('non-empty-array<array{commits: int<1, max>, files: int, insertions: int}>', $stats);
+ assertType('non-empty-array<array{commits: int, files: int, insertions: int}>', $stats);
- assertType('array{commits: int<1, max>, files: int, insertions: int}', $stats[$author]);
+ assertType('array{commits: int, files: int, insertions: int}', $stats[$author]); so plain int instead of integer-range |
You're right, the types were the same before ff2ce206be. The difference in the result is that we lost this error:
But we gained:
Which is kind of expected because after But the bug in the code was even before ff2ce206be. Which means we should fix it in code that existed even before your commit, probably somewhere in produceArrayDimFetchAssignValueToWrite or processAssignVar. On the line with But it's not a problem that originated in this PR so you don't really need to solve it 😊 Thank you. |
why does it matter whether php does not produce a warning for the deep fetch when the parent doesn't exist, as we use
IMO the snippet should not have any erros, right? |
Managed to fix it: 23dc4e7
Let's say that before assignment the type of When evaluating If it already exists, we're just rewriting an existing array key. If it does not exist, we're adding a new array item that just has |
The best feeling https://github.com/phpstan/phpstan-src/actions/runs/18133279748 🎉 |
nice 🤟 |
@ondrejmirtes I think your most recent fix created a regression when analyzing issue bot, see https://github.com/phpstan/phpstan-src/actions/runs/18134520219/job/51609654697?pr=4392 reduced to https://phpstan.org/r/34be5106-5d4a-49e0-ac9d-e8268bb46d67 |
I fixed this regression... by modifying your code 😅 f880231 No tests are failing so if you think you had a reason for this, please try to create a failing test. |
ouch :). the fix looks good to me. thank you! |
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