vimeo / psalm

A static analysis tool for finding errors in PHP applications
https://psalm.dev
MIT License
5.54k stars 660 forks source link

[Bug] Psalm doesn't understand variable cannot ever be null in the current scope #11050

Open dkarlovi opened 1 month ago

dkarlovi commented 1 month ago

Example https://psalm.dev/r/9bc102970e

This works, but changed behaviour https://psalm.dev/r/d3cb9314c1

Patched with @var https://psalm.dev/r/52a0ed404d

psalm-github-bot[bot] commented 1 month ago

I found these snippets:

https://psalm.dev/r/9bc102970e ```php */ class C { /** * @param TTranscript $items * * @return TTranscript */ private function accumulateTranscriptItems(array $items, float $threshold): array { $accumulatedItems = []; $currentItem = null; foreach ($items as $item) { if ($currentItem === null) { $currentItem = $item; } else { /** @psalm-trace $currentItem */ $currentItemEndTime = $currentItem['start'] + $currentItem['duration']; $gap = $item['start'] - $currentItemEndTime; if ($item['duration'] < $threshold && $gap <= $threshold) { $currentItem['text'] .= ' '.$item['text']; $currentItem['duration'] = max($currentItemEndTime, $item['start'] + $item['duration']) - $currentItem['start']; } else { $accumulatedItems[] = $currentItem; $currentItem = $item; } } } if ($currentItem !== null) { $accumulatedItems[] = $currentItem; } return $accumulatedItems; } } ``` ``` Psalm output (using commit 16b24bd): ERROR: PossiblyUndefinedArrayOffset - 23:39 - Possibly undefined array key $currentItem['start'] on array{duration: float|mixed, start?: float, text: string} INFO: MixedOperand - 23:63 - Right operand cannot be mixed INFO: MixedAssignment - 23:17 - Unable to determine the type that $currentItemEndTime is being assigned to INFO: Trace - 23:17 - $currentItem: array{duration: float|mixed, start?: float, text: string} INFO: MixedOperand - 24:41 - Right operand cannot be mixed INFO: MixedAssignment - 24:17 - Unable to determine the type that $gap is being assigned to INFO: MixedOperand - 28:48 - Left operand cannot be mixed INFO: MixedAssignment - 28:21 - Unable to determine the type of this assignment INFO: MixedReturnTypeCoercion - 40:16 - The type 'list{0?: array{duration: float|mixed, start?: float, text: string}, ...}' is more general than the declared return type 'list' for C::accumulateTranscriptItems INFO: MixedReturnTypeCoercion - 11:16 - The declared return type 'list' for C::accumulateTranscriptItems is more specific than the inferred return type 'list{0?: array{duration: float|mixed, start?: float, text: string}, ...}' ```
https://psalm.dev/r/d3cb9314c1 ```php */ class C { /** * @param TTranscript $items * * @return TTranscript */ private function accumulateTranscriptItems(array $items, float $threshold): array { $accumulatedItems = []; $currentItem = null; foreach ($items as $item) { if ($currentItem === null) { $currentItem = $item; } $currentItemEndTime = $currentItem['start'] + $currentItem['duration']; $gap = $item['start'] - $currentItemEndTime; if ($item['duration'] < $threshold && $gap <= $threshold) { $currentItem['text'] .= ' '.$item['text']; $currentItem['duration'] = max($currentItemEndTime, $item['start'] + $item['duration']) - $currentItem['start']; } else { $accumulatedItems[] = $currentItem; $currentItem = $item; } } if ($currentItem !== null) { $accumulatedItems[] = $currentItem; } return $accumulatedItems; } } ``` ``` Psalm output (using commit 16b24bd): No issues! ```
https://psalm.dev/r/52a0ed404d ```php */ class C { /** * @param TTranscript $items * * @return TTranscript */ private function accumulateTranscriptItems(array $items, float $threshold): array { $accumulatedItems = []; /** @var TTranscriptItem|null */ $currentItem = null; foreach ($items as $item) { if ($currentItem === null) { $currentItem = $item; } else { /** @psalm-trace $currentItem */ $currentItemEndTime = $currentItem['start'] + $currentItem['duration']; $gap = $item['start'] - $currentItemEndTime; if ($item['duration'] < $threshold && $gap <= $threshold) { $currentItem['text'] .= ' '.$item['text']; $currentItem['duration'] = max($currentItemEndTime, $item['start'] + $item['duration']) - $currentItem['start']; } else { $accumulatedItems[] = $currentItem; $currentItem = $item; } } } if ($currentItem !== null) { $accumulatedItems[] = $currentItem; } return $accumulatedItems; } } ``` ``` Psalm output (using commit 16b24bd): INFO: Trace - 25:17 - $currentItem: array{duration: float, start: float, text: string} ```