Summary
Psalm rejects the pseudo-type intersection non-empty-string&lowercase-string with:
InvalidDocblock: Intersection types must be all objects, Psalm\Type\Atomic\TNonEmptyString provided
The single-token equivalent non-empty-lowercase-string is accepted and works correctly, so the two forms are not interchangeable today.
This is closely related to #9628 (which covers literal-string&non-empty-string and friends), but I am filing this separately because it has a concrete real-world impact: laravel/framework's Illuminate\Support\Str::lower() docblock uses exactly this intersection.
Why it matters
Str::lower() in Laravel 13 ships with:
/**
* Convert the given string to lower-case.
*
* @param string $value
* @return ($value is '' ? '' : non-empty-string&lowercase-string)
*/
public static function lower($value)
Any Psalm user analysing a Laravel project therefore gets an InvalidDocblock out of the box on a widely used framework helper. Workarounds (stubbing the method to use non-empty-lowercase-string instead) exist but require plugin/stub-level overrides.
Reproduction
https://psalm.dev/r/b51da730d4
Output (Psalm 7.0.0-beta19):
ERROR: InvalidDocblock
Intersection types must be all objects, Psalm\Type\Atomic\TNonEmptyString provided
@return ($value is '' ? '' : non-empty-string&lowercase-string)
ERROR: InvalidDocblock
Intersection types must be all objects, Psalm\Type\Atomic\TNonEmptyString provided
@return non-empty-string&lowercase-string
Expected
non-empty-string&lowercase-string should be accepted and treated equivalently to the existing single-token pseudo-type non-empty-lowercase-string (which Psalm already supports). Ideally the same treatment would extend to the cases in #9628.
Environment
- Psalm 7.0.0-beta19
- PHP 8.5.5
Summary
Psalm rejects the pseudo-type intersection
non-empty-string&lowercase-stringwith:The single-token equivalent
non-empty-lowercase-stringis accepted and works correctly, so the two forms are not interchangeable today.This is closely related to #9628 (which covers
literal-string&non-empty-stringand friends), but I am filing this separately because it has a concrete real-world impact:laravel/framework'sIlluminate\Support\Str::lower()docblock uses exactly this intersection.Why it matters
Str::lower()in Laravel 13 ships with:Any Psalm user analysing a Laravel project therefore gets an
InvalidDocblockout of the box on a widely used framework helper. Workarounds (stubbing the method to usenon-empty-lowercase-stringinstead) exist but require plugin/stub-level overrides.Reproduction
https://psalm.dev/r/b51da730d4
Output (Psalm 7.0.0-beta19):
Expected
non-empty-string&lowercase-stringshould be accepted and treated equivalently to the existing single-token pseudo-typenon-empty-lowercase-string(which Psalm already supports). Ideally the same treatment would extend to the cases in #9628.Environment