diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index cd885a0c87b..385a954c946 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -65,8 +65,6 @@ use function array_merge; use function array_search; use function array_values; -use function assert; -use function class_exists; use function count; use function end; use function in_array; diff --git a/stubs/CoreImmutableClasses.phpstub b/stubs/CoreImmutableClasses.phpstub index f9c8d6e3e34..405f89b43cd 100644 --- a/stubs/CoreImmutableClasses.phpstub +++ b/stubs/CoreImmutableClasses.phpstub @@ -16,6 +16,24 @@ class DateTimeZone public function __construct(string $timezone) {} } +/** + * @psalm-immutable + * + * @template-covariant Start of string|DateTimeInterface + * @implements Traversable + */ +class DatePeriod implements Traversable +{ + const EXCLUDE_START_DATE = 1; + /** + * @param Start $start + * @param (Start is string ? 0|self::EXCLUDE_START_DATE : DateInterval) $interval + * @param (Start is string ? never : DateTimeInterface|positive-int) $end + * @param (Start is string ? never : 0|self::EXCLUDE_START_DATE) $options + */ + public function __construct($start, $interval = 0, $end = 1, $options = 0) {} +} + /** * @psalm-taint-specialize */ diff --git a/stubs/Php80.phpstub b/stubs/Php80.phpstub index 877b156f3a0..001ebf17ae6 100644 --- a/stubs/Php80.phpstub +++ b/stubs/Php80.phpstub @@ -82,3 +82,24 @@ class ReflectionUnionType extends ReflectionType { } class UnhandledMatchError extends Error {} + +/** + * @psalm-immutable + * + * @template-covariant Start of string|DateTimeInterface + * @implements IteratorAggregate + */ +class DatePeriod implements IteratorAggregate +{ + const EXCLUDE_START_DATE = 1; + /** + * @param Start $start + * @param (Start is string ? 0|self::EXCLUDE_START_DATE : DateInterval) $interval + * @param (Start is string ? never : DateTimeInterface|positive-int) $end + * @param (Start is string ? never : 0|self::EXCLUDE_START_DATE) $options + */ + public function __construct($start, $interval = 0, $end = 1, $options = 0) {} + + /** @psalm-return (Start is string ? (Traversable&Iterator) : (Traversable&Iterator)) */ + public function getIterator(): Iterator {} +} diff --git a/tests/CoreStubsTest.php b/tests/CoreStubsTest.php index 8bbe6435522..d6f0a85a916 100644 --- a/tests/CoreStubsTest.php +++ b/tests/CoreStubsTest.php @@ -33,5 +33,76 @@ public function providerValidCodeParse(): iterable 'error_levels' => [], 'php_version' => '8.0', ]; + yield 'Iterating over \DatePeriod (#5954) PHP7 Traversable' => [ + 'format("Y-m-d"); + }', + 'assertions' => [ + '$period' => 'DatePeriod', + '$dt' => 'DateTimeInterface|null' + ], + 'error_levels' => [], + 'php_version' => '7.3', + ]; + yield 'Iterating over \DatePeriod (#5954) PHP8 IteratorAggregate' => [ + 'format("Y-m-d"); + }', + 'assertions' => [ + '$period' => 'DatePeriod', + '$dt' => 'DateTimeImmutable|null' + ], + 'error_levels' => [], + 'php_version' => '8.0', + ]; + yield 'Iterating over \DatePeriod (#5954), ISO string' => [ + 'format("Y-m-d"); + }', + 'assertions' => [ + '$period' => 'DatePeriod', + '$dt' => 'DateTime|null' + ], + 'error_levels' => [], + 'php_version' => '8.0', + ]; + yield 'DatePeriod implements only Traversable on PHP 7' => [ + ' [], + 'error_levels' => [], + 'php_version' => '7.3', + ]; + yield 'DatePeriod implements IteratorAggregate on PHP 8' => [ + ' [], + 'error_levels' => ['RedundantCondition'], + 'php_version' => '8.0', + ]; } }