Skip to content

PHP 8.3: Saner array_(sum|product)() #1990

@afilina

Description

@afilina

Related to #1589

Analysis

The RFC proposes these 2 changes:

  • Emit warning on non-numeric types. Note: since the inspiration for this behavior change comes from array_reduce(), which throws a TypeError for invalid input types, it's reasonable to assume that in the future, these warnings might get promoted to errors.
  • For objects that implement the do_operation or cast_object handler internally (not userland), use that handler to cast the value.

Summary of existing behavior in PHP 8.2:

array_sum([null]); // 0

array_sum([false]); // 0

array_sum([true]); // 1

array_sum(['']); // 0

array_sum(['42.5']); // 42.5

array_sum(['42,5']); // 42

array_sum(['42a']); // 42

array_sum(['a42']); // 0

array_sum([[42]]); // 0 (regardless of inner array content)

array_sum([new stdClass()]); // 0

array_sum([gmp_init(42)]); // 0

Updated behavior in PHP 8.3:

array_sum([null]); // 0

array_sum([false]); // 0

array_sum([true]); // 1

// Warning: Addition is not supported on type string
array_sum(['']); // 0

array_sum(['42.5']); // 42.5

// Warning: A non-numeric value encountered
array_sum(['42,5']); // 42

// Warning: A non-numeric value encountered
array_sum(['42a']); // 42

// Warning: Addition is not supported on type string
array_sum(['a42']); // 0

// Warning: Addition is not supported on type array
array_sum([[42]]); // 0 (regardless of inner array content)

// Warning: Addition is not supported on type stdClass
array_sum([new stdClass()]); // 0

array_sum([gmp_init(42)]); // 42 (whereas PHP 8.2 treated this like any other object and resulted in 0)

Top 2000 Packages

Found 270 occurrences of array_sum calls, and 5 occurrences of array_product calls. All usages rely on a variable input.

GMP and FFI functions seem to be used even more frequently, so we should definitely consider them when checking castability.

Detection in PHP 8.2

Since behavior is the same for array_sum() and array_product(), I'll focus on array_sum():

  • array_sum(['']) valid
  • array_sum(['42a']) valid
  • array_sum(['a42']) valid
  • array_sum([[]]) valid
  • array_sum([new stdClass()]) valid
  • array_sum([gmp_init(42)]) valid
  • array_sum([(int)'a42']) valid

See "Syntax Variations" for false positives and behavior that remains unchanged.

Detection in PHP 8.3

Since behavior is the same for array_sum() and array_product(), I'll focus on array_sum():

  • array_sum(['']) warning
  • array_sum(['42a']) warning
  • array_sum(['a42']) warning
  • array_sum([[]]) warning
  • array_sum([new stdClass()]) warning
  • array_sum([gmp_init(42)]) result changes because GMP can cast to int internally (I recommend treating this as a warning in PHPCompatibility)
  • array_sum([(int)'a42']) valid, no behavior change

See "Syntax Variations" for false positives and behavior that remains unchanged.

Syntax Variations

Detectability

  • Find calls to array_sum() or array_product() ✅ Trivial in PHPCompatibility
  • Find values passed to these functions ✅❌ Limited ability, as these can come from anywhere
  • Find types of array elements ✅❌ Depends on finding the values, detecting the type itself should be straightforward in many cases
  • Detect usage of gmp_* functions in array elements ✅❌ Depends on finding the values, detecting the gmp_* call itself should be straightforward in many cases (as far as I can tell, every such function returns a GMP type, which is castable)
  • Detect usage of FFI functions in array elements ✅❌ Depends on finding the values

It's unclear which other built-in classes implement the do_operation or cast_object handlers, and my C proficiency is insufficient to determine that myself. Should we enumerate them for this sniff?

This seems to be a job for a static analysis tool that can trace input and infer types. Opened tickets for both PHPStan and Psalm regardless of whether we'll create a sniff here.

References

Edit: added explicit casting of values in examples and detection

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