Skip to content

Intersection non-empty-string&lowercase-string rejected as "must be all objects" (used by laravel/framework Str::lower) #11821

@alies-dev

Description

@alies-dev

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions