Skip to content

PHP 8.3: Define proper semantics for range() function #1991

@afilina

Description

@afilina

Related to #1589

Analysis

The RFC proposes multiple changes in PHP 8.3.

1. If $step is a float but is compatible with int, interpret it as an integer

Syntax PHP 8.2 PHP 8.3
range(3, 2, 1.0) [3.0, 2.0] [3, 2]

https://3v4l.org/tvH5N

2. TypeErrors thrown when passing objects, resources, and arrays to range()

Syntax PHP 8.2 PHP 8.3
range(gmp_init(3), 2, 1) [3, 2] (some types, like GMP, can be converted to int) [3, 2]
range([3], 2, 1) [1, 2] ❌ TypeError: Argument #1 ($start) must be of type...
range(new stdClass(), 2, 1) [1, 2] ⚠️ Warning: ... could not be converted to int ❌ TypeError: Argument #1 ($start) must be of type...
range(STDIN, 2, 1) [1, 2] ❌ TypeError: Argument #1 ($start) must be of type...

https://3v4l.org/PDXYh

3. Deprecation warning to be emitted when passing null

Syntax PHP 8.2 PHP 8.3
range(null, 2, 1) [0, 1, 2] [0, 1, 2] ⚠️ Deprecated: Passing null to parameter #1 ($start)...

https://3v4l.org/v8jNN

4. Emit an E_WARNING when $start or $end is the empty string, and cast the value to 0

Syntax PHP 8.2 PHP 8.3
range('', 2, 1) [0, 1, 2] [0, 1, 2] ⚠️ Warning: Argument #1 ($start) must not be empty, casted to 0

https://3v4l.org/WZWUY

5. Emit an E_WARNING when $start or $end has more than one byte if it is a non-numeric string

Syntax PHP 8.2 PHP 8.3
range('3a', '2a', 1) ["3", "2"] ["3", "2"] ⚠️ Warning: ... subsequent bytes are ignored x 2
range('a3', 'a2', 1) ["a"] ["a"] ⚠️ Warning: ... subsequent bytes are ignored x 2

https://3v4l.org/mCtv5

6. Emit an E_WARNING when $start or $end is cast to an integer because the other boundary input is a number

Syntax PHP 8.2 PHP 8.3
range(3, 'a', 1) [3, 2, 1, 0] [3, 2, 1, 0] ⚠️ Warning: ... argument #2 ($end) converted to 0

https://3v4l.org/WoOic

Note: when the string is empty, the result is the same, but the warning differs slightly ("casted" vs "converted"). See point 4 above.

7. Emit an E_WARNING when $step is a float when trying to generate a range of characters, except if both boundary inputs are numeric strings

Syntax PHP 8.2 PHP 8.3
range('5', '6', 0.5) [5.0, 5.5, 6.0] [5.0, 5.5, 6.0] (no change)
range('a', 'b', 0.5) [0.0] [0.0] ⚠️ Warning: Argument #3 ($step) must be of type int when generating an array of characters, inputs converted to 0

https://3v4l.org/3v9gd

8. Throw value errors if $start, $end, or $step is a non-finite float (-INF, INF, NAN)

Syntax PHP 8.2 PHP 8.3
range(3, 2, -INF) ❌ ValueError: ... must not exceed the specified range ❌ ValueError: ... must be a finite number, INF provided
range(3, 2, INF) ❌ ValueError: ... must not exceed the specified range ❌ ValueError: ... must be a finite number, INF provided
range(3, 2, NAN) ❌ ValueError: ... must not exceed the specified range ❌ ValueError: ... must be a finite number, NAN provided

https://3v4l.org/njIN7

9. Throw a more descriptive ValueError when $step is zero

Syntax PHP 8.2 PHP 8.3
range(3, 2, 0) ❌ ValueError: ... must not exceed the specified range ❌ ValueError: ... must be a finite number, INF provided

https://3v4l.org/8MTLO

10. Throw a ValueError when passing a negative $step for increasing ranges

Syntax PHP 8.2 PHP 8.3
range(3, 2, -1) [3, 2] [3, 2] (no change for decreasing ranges)
range(3, 4, -1) [3, 4] ❌ ValueError: ... must be greater than 0 for increasing ranges

https://3v4l.org/vIamn

11. Produce a list of characters if one of the boundary inputs is a string digit instead of casting the other input to int

Syntax PHP 8.2 PHP 8.3
range('9', 'a', 1) [9, 8, 7, ...] (10 int values) ["9", ":", ";", "<", ...] (41 ASCII values)

https://3v4l.org/SZh8M

12. Other behavior tested

Syntax PHP 8.2 PHP 8.3
range(false, 2, 1) [0, 1, 2] [0, 1, 2] (no change)
range(true, 2, 1) [1, 2] [1, 2] (no change)
range('3.5', 2, 1) [3, 2] [3.5, 2.5] (now a float)

https://3v4l.org/vk4nK

Note: I couldn't find the last behavior change in the RFC, migration docs, or the range() changelog (see references below).

Top 2000 Packages

Analysis already performed and documented by the RFC's author.

Detection in PHP 8.2

Since this is a change in semantics, no detection in PHP 8.2 is required. Pre-existing errors and warnings are out of scope for PHPCompatibility (@jrfnl please confirm).

Detection in PHP 8.3

  • Calls with arguments that become ValueErrors or TypeErrors: ❌ Invalid, error
  • Calls with arguments that become warnings: ️⚠️ Invalid, warning
  • Calls with arguments resulting in other behavior changes, like becoming floats in range('3.5', 2, 1): ️⚠️ BC break, warning

Syntax Variations

Detectability

  • Calls to the range native function: ✅ Detectable with PHPCompatibility.
  • Argument types and values passed to range: ⚠️ Limitations in PHPCompatibility, but the prevalence of literals (per the RFC) suggests we should be able to detect a significant number of cases.

References

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