Skip to content

Fix GH-10497: Allow const obj->prop = value#20903

Open
khaledalam wants to merge 4 commits into
php:masterfrom
khaledalam:fix-gh10497-allow-const-object-modification
Open

Fix GH-10497: Allow const obj->prop = value#20903
khaledalam wants to merge 4 commits into
php:masterfrom
khaledalam:fix-gh10497-allow-const-object-modification

Conversation

@khaledalam

@khaledalam khaledalam commented Jan 11, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #10497 - Allows direct modification of object properties stored in constants, for both global constants and class constants.

RFC: https://wiki.php.net/rfc/const_object_property_write

Problem

Previously, this code would fail with a fatal error:

const a = new stdClass;
a->b = 42;  // Fatal error: Cannot use temporary expression in write context

The constant binding is immutable, but the object it holds is mutable by design — so the write should be allowed.

Solution

In zend_delayed_compile_prop(), when the object expression is a constant used in a write context (BP_VAR_W/RW/UNSET/FUNC_ARG), fetch the constant at runtime instead of treating it as a temporary:

  • ZEND_AST_CONST → emit ZEND_FETCH_CONSTANT (global constants)
  • ZEND_AST_CLASS_CONST → emit ZEND_FETCH_CLASS_CONSTANT (class constants)

Because the constant is fetched at runtime (not compile-time evaluated), the property write targets the real object.
The constant binding itself is never modified.

Scope

Supported:

  • CONST->prop = value; // global constant
  • Cls::CONST->prop = value; // class constant (via class name)
  • $obj::CONST->prop = value; // class constant (via instance)
  • …including compound assignment, ++/--, .=, isset()/unset(), by-reference passing, and nested chains.

Still rejected (unchanged):

  • Rebinding a constant (CONST = ...) — parse error
  • Dimension/array writes on constants (CONST[0] = ..., CONST["x"] = ...) , these write to a temporary, so they remain a compile error

Backward incompatible change

Enum cases are class constants, so they're affected. Writing to or unsetting a readonly case property, e.g. unset(Enum::Case->value) or Enum::Case->value = x , now raises the standard runtime readonly-property Error instead of the previous compile-time "Cannot use temporary expression in write context" fatal. This matches plain variable access ($x = Enum::Case; unset($x->value);). Enum case properties remain immutable.

@iluuu1994 iluuu1994 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.

Thanks @khaledalam! Looks reasonable overall.

Comment thread Zend/zend_compile.c Outdated
Comment thread Zend/zend_compile.c Outdated
Comment thread Zend/zend_compile.c Outdated
@khaledalam

Copy link
Copy Markdown
Contributor Author

Thanks @khaledalam! Looks reasonable overall.

Thanks @iluuu1994 for the feedback, all comments addressed.

@iluuu1994

Copy link
Copy Markdown
Member

Cool, thanks @khaledalam! This is a language change and should be discussed on the internals mailing list at least. I think this will impact GH-13800, but that's not a major issue.

@iluuu1994

Copy link
Copy Markdown
Member

Btw, one thing in particular people might object to is:

const arrayTest = [1, 2, 3];
arrayTest[] = 4;
var_dump(arrayTest);

This becomes valid, but prints:

array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}

I.e. the assignment has no effect, because it happens on a temporary value. Make sure to mention this in your e-mail to internals.

@khaledalam

Copy link
Copy Markdown
Contributor Author

I've opened an internals thread and sent an email for discussion: https://news-web.php.net/php.internals/129757

@khaledalam khaledalam requested a review from iluuu1994 January 31, 2026 00:03
@iluuu1994

Copy link
Copy Markdown
Member

It sounds like @TimWolla objected, so this would need an RFC, or at least the mitigation from https://news-web.php.net/php.internals/129839.

@TimWolla TimWolla 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.

Yes, my replies on the list were an objection against the PR as-is. Selecting request changes for visibility.

@khaledalam

Copy link
Copy Markdown
Contributor Author

Noted, I'll look into it

@khaledalam khaledalam force-pushed the fix-gh10497-allow-const-object-modification branch from d488d10 to b0a2437 Compare April 3, 2026 23:52
Pass type parameter to zend_compile_const() instead of bool use_tmp
Check BP_VAR_R/BP_VAR_IS for read mode
Remove unnecessary wrapper function
Return void instead of zend_op*
@khaledalam khaledalam force-pushed the fix-gh10497-allow-const-object-modification branch from b0a2437 to 2bd4eb9 Compare June 21, 2026 18:31
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.

Cannot directly modify an object stored in a constant

3 participants