Infer numeric-string for DateInterval::format('%a') when interval comes from diff()#5674
Infer numeric-string for DateInterval::format('%a') when interval comes from diff()#5674phpstan-bot wants to merge 5 commits into
numeric-string for DateInterval::format('%a') when interval comes from diff()#5674Conversation
… comes from `diff()` - Extract shared format type inference logic into DateIntervalFormatReturnTypeHelper - Check if the receiver's `days` property is narrowed to `int` (indicating the interval came from DateTimeInterface::diff() or date_diff()) - When `days` is `int`, use a diff-created DateInterval for evaluating `%a` format specifier, which produces a numeric string instead of "(unknown)" - Add DateIntervalFormatFunctionReturnTypeExtension for the procedural date_interval_format() function, which previously had no type inference
Use a single $dateInterval selected based on $daysIsInt instead of maintaining two separate intervals and checking str_contains for %a. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
staabm
left a comment
There was a problem hiding this comment.
why does issuebot not report phpstan/phpstan#1452 will be fixed?
| } | ||
|
|
||
| $daysIsInt = $intervalType->hasInstanceProperty('days')->yes() | ||
| && (new IntegerType())->isSuperTypeOf($intervalType->getInstanceProperty('days', $scope)->getReadableType())->yes(); |
There was a problem hiding this comment.
could use isInteger() instead
There was a problem hiding this comment.
Done. Applied staabm's review feedback: replaced (new IntegerType())->isSuperTypeOf(...)->yes() with the simpler ->isInteger()->yes() shortcut method, and removed the now-unused IntegerType import. Tests and static analysis pass.
Regarding the issuebot question — that's about the GitHub issue bot's behavior (not reporting that #1452 will be fixed), which isn't something addressable in code changes here.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dunno, maybe it doesnt work well with old issue using the older playground Still the bug is fixed to me accordîg to the test added |
staabm
left a comment
There was a problem hiding this comment.
This needs a rule test showing the original error no longer is emitted
…ormat('%a') * 60
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Done. Added a rule test ( |
Summary
DateInterval::format('%a')was incorrectly inferred asnon-empty-string(not numeric) even when the DateInterval came fromDateTimeInterface::diff(). This caused false positives when usingformat('%a')in arithmetic operations like$interval->format('%a') * 60.The root cause: the extension evaluated format specifiers against
new DateInterval('P0D'), where%areturns(unknown)becausedaysisfalse. But afterdiff(),daysis narrowed tointand%areturns a numeric string.Changes
DateIntervalFormatDynamicReturnTypeExtensioninto a newDateIntervalFormatReturnTypeHelper(src/Type/Php/DateIntervalFormatReturnTypeHelper.php)daysproperty isint(indicating it came fromdiff()ordate_diff())daysisint, a diff-created DateInterval is used for evaluating%a, producing correct numeric-string inferenceDateIntervalFormatFunctionReturnTypeExtension(src/Type/Php/DateIntervalFormatFunctionReturnTypeExtension.php) for the proceduraldate_interval_format()function, which previously had no dynamic return type inference at allDateIntervalFormatDynamicReturnTypeExtensionto delegate to the helperRoot cause
The
DateIntervalFormatDynamicReturnTypeExtensiontested all format specifiers against a freshnew DateInterval('P0D'). For%a(total days), this produces(unknown)since thedaysproperty isfalsewhen the interval was not created viadiff(). However, PHPStan's stubs already narrowdiff()to returnDateInterval&object{days:int}, which means the%aspecifier will always produce a numeric string in that context.Test
tests/PHPStan/Analyser/nsrt/bug-1452.phptesting:DateTimeImmutable::diff()+format('%a')→numeric-stringDateTime::diff()+format('%a')→numeric-stringDateTimeInterface::diff()+format('%a')→numeric-stringdate_diff()+format('%a')→numeric-string%R%acombination →numeric-string(since+0is numeric)DateInterval(not from diff) +format('%a')→non-empty-string(not numeric)date_interval_format()function with both diff and non-diff intervalsformat('%a')result producesfloat|int(no error)Fixes phpstan/phpstan#1452