Skip to content

Fix type inference of compound assignment to typed properties - #24107

Open
ndossche wants to merge 2 commits into
php:PHP-8.4from
ndossche:fix-assign-obj-op-prop-coercion
Open

ndossche wants to merge 2 commits into
php:PHP-8.4from
ndossche:fix-assign-obj-op-prop-coercion

Conversation

@ndossche

@ndossche ndossche commented Oct 3, 2026

Copy link
Copy Markdown
Member

The result of ASSIGN_OBJ_OP and ASSIGN_STATIC_PROP_OP is coerced to the property type, but only DOUBLE->LONG and LONG/DOUBLE->STRING coercions were modelled. Other coercions resulted in an empty or too narrow type. When the property is unknown, BOOL and DOUBLE were also missing.

Inferred result type of $o->p OP= $v in weak mode:

Property Value Old New Runtime
int float long long int
?int float long long int
int|float float double double float
int|false float long long int
string int string string string
string|bool int string string string
bool int (empty) bool bool
float int (empty) double float
float|bool string (empty) double|bool float, bool if non-numeric
int|string float long long|string int, string for INF/NAN
int|bool float long long|bool int, bool for INF/NAN
(unknown) any +long|string +scalar any scalar

In strict mode, only int -> float coercion remains, e.g. a float property with an int value infers double, and an unknown property only adds double to the value type.

The result of ASSIGN_OBJ_OP and ASSIGN_STATIC_PROP_OP is coerced to the
property type, but only DOUBLE->LONG and LONG/DOUBLE->STRING coercions were
modelled. Other coercions resulted in an empty or too narrow type.
When the property is unknown, BOOL and DOUBLE were also missing.

Inferred result type of `$o->p OP= $v` in weak mode:

  property   | value  | old        | new          | runtime
  -----------+--------+------------+--------------+------------------------------
  int        | float  | long       | long         | int
  ?int       | float  | long       | long         | int
  int|float  | float  | double     | double       | float
  int|false  | float  | long       | long         | int
  string     | int    | string     | string       | string
  string|bool| int    | string     | string       | string
  bool       | int    | (empty)    | bool         | bool
  float      | int    | (empty)    | double       | float
  float|bool | string | (empty)    | double|bool  | float, bool if non-numeric
  int|string | float  | long       | long|string  | int, string for INF/NAN
  int|bool   | float  | long       | long|bool    | int, bool for INF/NAN
  (unknown)  | any    | +long|str  | +scalar      | any scalar

In strict mode, only int -> float coercion remains, e.g. a float property
with an int value infers double, and an unknown property only adds double
to the value type.

@arnaud-lb arnaud-lb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me otherwise

Comment on lines +2575 to +2576
/* The result of a compound assignment to a property must satisfy the property type.
* A value that doesn't may be coerced to one of the scalar types of the property. */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing, but this is not true for unset properties with a __get:

class B {
    public bool $b = false;
    function __construct() { unset($this->b); }
    function __get($n) { return 100; }
}
function f(B $o) { $r = ($o->b += 1); return [is_bool($r), $r]; }
var_dump(f(new B));
// opcache off: [false, int(2)]   opcache on: [true, int(2)]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... fantastic (not)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants