diff --git a/src/Type/Php/DateIntervalFormatDynamicReturnTypeExtension.php b/src/Type/Php/DateIntervalFormatDynamicReturnTypeExtension.php new file mode 100644 index 0000000000..f6d32b02ed --- /dev/null +++ b/src/Type/Php/DateIntervalFormatDynamicReturnTypeExtension.php @@ -0,0 +1,91 @@ +getName() === 'format'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + $arguments = $methodCall->getArgs(); + + if (!isset($arguments[0])) { + return null; + } + + $arg = $scope->getType($arguments[0]->value); + + $constantStrings = $arg->getConstantStrings(); + if (count($constantStrings) === 0) { + if ($arg->isNonEmptyString()->yes()) { + return new IntersectionType([new StringType(), new AccessoryNonEmptyStringType()]); + } + + return null; + } + + // The worst case scenario for the non-falsy-string check is that every number is 0. + $dateInterval = new DateInterval('P0D'); + + $possibleReturnTypes = []; + foreach ($constantStrings as $string) { + $value = $dateInterval->format($string->getValue()); + + $accessories = []; + if (is_numeric($value)) { + $accessories[] = new AccessoryNumericStringType(); + } + if ($value !== '0' && $value !== '') { + $accessories[] = new AccessoryNonFalsyStringType(); + } elseif ($value !== '') { + $accessories[] = new AccessoryNonEmptyStringType(); + } + if (strtolower($value) === $value) { + $accessories[] = new AccessoryLowercaseStringType(); + } + if (strtoupper($value) === $value) { + $accessories[] = new AccessoryUppercaseStringType(); + } + + if (count($accessories) === 0) { + return null; + } + + $possibleReturnTypes[] = new IntersectionType([new StringType(), ...$accessories]); + } + + return TypeCombinator::union(...$possibleReturnTypes); + } + +} diff --git a/tests/PHPStan/Analyser/nsrt/date-interval-format.php b/tests/PHPStan/Analyser/nsrt/date-interval-format.php new file mode 100644 index 0000000000..691d9e3328 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/date-interval-format.php @@ -0,0 +1,40 @@ +format($string)); + assertType('non-empty-string', $dateInterval->format($nonEmptyString)); + + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $dateInterval->format('%Y')); // '00' + assertType('lowercase-string&non-empty-string&numeric-string&uppercase-string', $dateInterval->format('%y')); // '0' + assertType('lowercase-string&non-falsy-string&numeric-string&uppercase-string', $dateInterval->format($unionString1)); + assertType('lowercase-string&non-empty-string&numeric-string&uppercase-string', $dateInterval->format($unionString2)); + + assertType('non-falsy-string&uppercase-string', $dateInterval->format('%Y DAYS')); + assertType('non-falsy-string&uppercase-string', $dateInterval->format($unionString1. ' DAYS')); + + assertType('lowercase-string&non-falsy-string', $dateInterval->format('%Y days')); + assertType('lowercase-string&non-falsy-string', $dateInterval->format($unionString1. ' days')); + } +}