Skip to content

Infer literal types for word date format characters - #6838

Draft
westonruter wants to merge 2 commits into
phpstan:2.3.xfrom
westonruter:date-format-word-types
Draft

westonruter wants to merge 2 commits into
phpstan:2.3.xfrom
westonruter:date-format-word-types

Conversation

@westonruter

Copy link
Copy Markdown

The a, A, S, D, l, M, and F format characters of date(), date_format(), and DateTimeInterface::format() now produce unions of their possible values, alongside the numeric single-character formats added in #721. PHP formats these characters from hard-coded English tables in ext/date/php_date.c, so the values do not depend on the locale.

Format Type
a 'am'|'pm'
A 'AM'|'PM'
S 'nd'|'rd'|'st'|'th'
D 'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'
l 'Friday'|'Monday'|'Saturday'|'Sunday'|'Thursday'|'Tuesday'|'Wednesday'
M 'Apr'|'Aug'|'Dec'|'Feb'|'Jan'|'Jul'|'Jun'|'Mar'|'May'|'Nov'|'Oct'|'Sep'
F 'April'|'August'|'December'|'February'|'January'|'July'|'June'|'March'|'May'|'November'|'October'|'September'

Removal of the non-empty-string widening

Adding the cases alone is not enough: getTypeFromFormatType() ended with

if ($type->isNumericString()->no() && $formatType->isNonEmptyString()->yes()) {
	$type = TypeCombinator::union($type, new IntersectionType([
		new StringType(), new AccessoryNonEmptyStringType(),
	]));
}

which widens any non-numeric constant result to non-empty-string, so the new unions came out as non-empty-string. The same happens today for formats without format characters, e.g. date(' ') is non-empty-string rather than ' '.

This block was added in #2888 for phpstan/phpstan#10468 (a non-empty format should give a non-empty result). That guarantee does not depend on it:

  • A non-constant format is handled by the count($types) === 0 branch, which already returns non-empty-string for a non-empty-string format.
  • For a constant non-empty format, every type buildReturnTypeFromFormat() returns is already non-empty: the explicit cases, ConstantStringType($date), numeric-string, and non-falsy-string.

So the block is removed rather than worked around. The bug-10468.php and bug-6613.php tests still pass.

Tests

  • nsrt/bug-15537.php is the reproducer from the issue. It fails before this change (non-falsy-string for every assertion) and passes after.
  • nsrt/date-format.php expected non-falsy-string for D; it now expects the weekday union.

Closes phpstan/phpstan#15537

🤖 Generated with Claude Code

The `a`, `A`, `S`, `D`, `l`, `M`, and `F` format characters of `date()`,
`date_format()`, and `DateTimeInterface::format()` now produce unions of
their possible values, like the numeric single-character formats already do.
PHP formats these characters from hard-coded English tables, so the values
do not depend on the locale.

The final `union()` with `non-empty-string` in `getTypeFromFormatType()` is
removed, since it would otherwise widen these literal unions (and any other
non-numeric constant result, such as `date(' ')`) to `non-empty-string`.
A non-empty format yielding a non-empty result is already guaranteed: a
non-constant format is handled by the `non-empty-string` branch above, and
every type built for a constant non-empty format is itself non-empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "S" date format returns English ordinal suffixes, and typos reports the
"nd" string literal as a misspelling of "and".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Infer literal types for the a, A, S, D, l, M, and F date format characters

1 participant