Skip to content

[RFC] Add support for attributes on compile-time constants #16952

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ PHP 8.5 UPGRADE NOTES
. Added asymmetric visibility support for static properties.
RFC: https://wiki.php.net/rfc/static-aviz
. Added support for casts in constant expressions.
. Added support for attributes on compile-time non-class constants.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
. Added support for attributes on compile-time non-class constants.
. Added support for attributes on global constants.

Global constants are not actually compile time in PHP.

Copy link
Member Author

Choose a reason for hiding this comment

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

They aren't created at compile time, but they are declared at compile time, unlike those with define(). https://www.php.net/manual/en/language.constants.syntax.php

As opposed to defining constants using define(), constants defined using the const keyword must be declared at the top-level scope because they are defined at compile-time.

Copy link
Member Author

Choose a reason for hiding this comment

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

Since the UPGRADING file keeps causing merge conflicts, I've removed my changes to that file for now, but the language I'm planning to include is:

  . Added support for attributes on compile-time non-class constants.
    RFC: https://wiki.php.net/rfc/attributes-on-constants
  . The #[\Deprecated] attribute can now be used on constants.
    RFC: https://wiki.php.net/rfc/attributes-on-constants

RFC: https://wiki.php.net/rfc/attributes-on-constants
. The #[\Deprecated] attribute can now be used on constants.
RFC: https://wiki.php.net/rfc/attributes-on-constants

- Curl:
. Added support for share handles that are persisted across multiple PHP
Expand Down Expand Up @@ -337,6 +341,8 @@ PHP 8.5 UPGRADE NOTES
. ReflectionConstant::getFileName() was introduced.
. ReflectionConstant::getExtension() and
ReflectionConstant::getExtensionName() were introduced.
. ReflectionConstant::getAttributes() was introduced.
RFC: https://wiki.php.net/rfc/attributes-on-constants

========================================
7. New Classes and Interfaces
Expand Down
14 changes: 13 additions & 1 deletion Zend/tests/attributes/001_placement.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ $f2 = #[A1(9)] function () { };

$f3 = #[A1(10)] fn () => 1;

#[A1(11)]
const CT_CONSTANT = 'Demo';

$ref = new \ReflectionClass(Foo::class);

$sources = [
Expand All @@ -37,7 +40,8 @@ $sources = [
new \ReflectionObject($object),
new \ReflectionFunction('f1'),
new \ReflectionFunction($f2),
new \ReflectionFunction($f3)
new \ReflectionFunction($f3),
new \ReflectionConstant('CT_CONSTANT'),
];

foreach ($sources as $r) {
Expand Down Expand Up @@ -132,3 +136,11 @@ array(1) {
[0]=>
int(10)
}

string(18) "ReflectionConstant"
int(1)
string(2) "A1"
array(1) {
[0]=>
int(11)
}
5 changes: 5 additions & 0 deletions Zend/tests/attributes/029_reflect_internal_symbols.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ var_dump($rcc->getAttributes());
$rp = new ReflectionProperty('Exception', 'message');
var_dump($rp->getAttributes());

$rct = new ReflectionConstant('PHP_VERSION');
var_dump($rct->getAttributes());

?>
--EXPECT--
array(0) {
Expand All @@ -30,3 +33,5 @@ array(0) {
}
array(0) {
}
array(0) {
}
39 changes: 39 additions & 0 deletions Zend/tests/attributes/034_target_values.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
--TEST--
Attribute flags are all different, TARGET_ALL includes all targets
--FILE--
<?php

function showFlag(string $name, int $value) {
$all = Attribute::TARGET_ALL;
$and = $all & $value;
echo "Attribute::$name = $value ($all & $value === $and)\n";
}

showFlag("TARGET_CLASS", Attribute::TARGET_CLASS);
showFlag("TARGET_FUNCTION", Attribute::TARGET_FUNCTION);
showFlag("TARGET_METHOD", Attribute::TARGET_METHOD);
showFlag("TARGET_PROPERTY", Attribute::TARGET_PROPERTY);
showFlag("TARGET_CLASS_CONSTANT", Attribute::TARGET_CLASS_CONSTANT);
showFlag("TARGET_PARAMETER", Attribute::TARGET_PARAMETER);
showFlag("TARGET_CONSTANT", Attribute::TARGET_CONSTANT);
showFlag("IS_REPEATABLE", Attribute::IS_REPEATABLE);

$all = Attribute::TARGET_CLASS | Attribute::TARGET_FUNCTION
| Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY
| Attribute::TARGET_CLASS_CONSTANT | Attribute::TARGET_PARAMETER
| Attribute::TARGET_CONSTANT;
var_dump($all, Attribute::TARGET_ALL, $all === Attribute::TARGET_ALL);

?>
--EXPECT--
Attribute::TARGET_CLASS = 1 (127 & 1 === 1)
Attribute::TARGET_FUNCTION = 2 (127 & 2 === 2)
Attribute::TARGET_METHOD = 4 (127 & 4 === 4)
Attribute::TARGET_PROPERTY = 8 (127 & 8 === 8)
Attribute::TARGET_CLASS_CONSTANT = 16 (127 & 16 === 16)
Attribute::TARGET_PARAMETER = 32 (127 & 32 === 32)
Attribute::TARGET_CONSTANT = 64 (127 & 64 === 64)
Attribute::IS_REPEATABLE = 128 (127 & 128 === 0)
int(127)
int(127)
bool(true)
39 changes: 39 additions & 0 deletions Zend/tests/attributes/constants/allow_named_parameters.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
--TEST--
Verify that named parameters can be passed to attributes on constants
--FILE--
<?php

#[Attribute]
class MyAttribute {
public function __construct($first, $second) {
echo "first: $first\n";
echo "second: $second\n";
}
}

#[MyAttribute(second: "bar", first: "foo")]
const EXAMPLE = 'ignored';

$ref = new ReflectionConstant('EXAMPLE');
$attribs = $ref->getAttributes();
var_dump($attribs);
var_dump($attribs[0]->getArguments());
$attribs[0]->newInstance();

?>
--EXPECTF--
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(11) "MyAttribute"
}
}
array(2) {
["second"]=>
string(3) "bar"
["first"]=>
string(3) "foo"
}
first: foo
second: bar
34 changes: 34 additions & 0 deletions Zend/tests/attributes/constants/ast_export.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
AST can be recreated when constants have attributes
--EXTENSIONS--
zend_test
--FILE--
<?php

#[MyAttrib]
const WITH_ATTRIBUTE = true;

#[First]
#[Second]
const WITH_UNGROUPED = true;

#[First, Second]
const WITH_GROUPED = true;

#[MyAttrib(5, param: "example")]
const WITH_PARAMETERS = true;

echo zend_test_compile_to_ast(file_get_contents(__FILE__));

?>
--EXPECT--
#[MyAttrib]
const WITH_ATTRIBUTE = true;
#[First]
#[Second]
const WITH_UNGROUPED = true;
#[First, Second]
const WITH_GROUPED = true;
#[MyAttrib(5, param: 'example')]
const WITH_PARAMETERS = true;
echo zend_test_compile_to_ast(file_get_contents(__FILE__));
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Constants listed in valid targets when used wrong (internal attribute)
--FILE--
<?php

#[Deprecated]
class Example {}

?>
--EXPECTF--
Fatal error: Attribute "Deprecated" cannot target class (allowed targets: function, method, class constant, constant) in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Constants listed in valid targets when used wrong (userland attribute)
--FILE--
<?php

#[Attribute(Attribute::TARGET_CONSTANT)]
class MyConstantAttribute {}

#[MyConstantAttribute]
class Example {}

$ref = new ReflectionClass(Example::class);
$attribs = $ref->getAttributes();
var_dump($attribs);
$attribs[0]->newInstance();

?>
--EXPECTF--
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(19) "MyConstantAttribute"
}
}

Fatal error: Uncaught Error: Attribute "MyConstantAttribute" cannot target class (allowed targets: constant) in %s:%d
Stack trace:
#0 %s(%d): ReflectionAttribute->newInstance()
#1 {main}
thrown in %s on line %d
21 changes: 21 additions & 0 deletions Zend/tests/attributes/constants/constant_redefined_addition.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
If a constant is redefined, attributes remain unchanged (no attributes)
--FILE--
<?php

const MY_CONST = "No attributes";

#[\MyAttribute]
const MY_CONST = "Has attributes";

echo MY_CONST . "\n";

$reflection = new ReflectionConstant('MY_CONST');
var_dump($reflection->getAttributes())

?>
--EXPECTF--
Warning: Constant MY_CONST already defined in %s on line %d
No attributes
array(0) {
}
27 changes: 27 additions & 0 deletions Zend/tests/attributes/constants/constant_redefined_change.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
If a constant is redefined, attributes remain unchanged (different attributes)
--FILE--
<?php

#[\MyAttribute]
const MY_CONST = "Has attributes (1)";

#[\MyOtherAttribute]
const MY_CONST = "Has attributes (2)";

echo MY_CONST . "\n";

$reflection = new ReflectionConstant('MY_CONST');
var_dump($reflection->getAttributes())

?>
--EXPECTF--
Warning: Constant MY_CONST already defined in %s on line %d
Has attributes (1)
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(11) "MyAttribute"
}
}
26 changes: 26 additions & 0 deletions Zend/tests/attributes/constants/constant_redefined_removal.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
If a constant is redefined, attributes remain unchanged (had attributes)
--FILE--
<?php

#[\MyAttribute]
const MY_CONST = "Has attributes";

const MY_CONST = "No attributes";

echo MY_CONST . "\n";

$reflection = new ReflectionConstant('MY_CONST');
var_dump($reflection->getAttributes())

?>
--EXPECTF--
Warning: Constant MY_CONST already defined in %s on line %d
Has attributes
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(11) "MyAttribute"
}
}
25 changes: 25 additions & 0 deletions Zend/tests/attributes/constants/multiple_attributes_grouped.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
Multiple attributes in a group are allowed
--FILE--
<?php

#[\Foo, \Bar]
const CONSTANT = 1;

$ref = new ReflectionConstant('CONSTANT');
var_dump($ref->getAttributes());

?>
--EXPECTF--
array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(3) "Foo"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(3) "Bar"
}
}
26 changes: 26 additions & 0 deletions Zend/tests/attributes/constants/multiple_attributes_ungrouped.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Multiple attributes in separate groups are allowed
--FILE--
<?php

#[\Foo]
#[\Bar]
const CONSTANT = 1;

$ref = new ReflectionConstant('CONSTANT');
var_dump($ref->getAttributes());

?>
--EXPECTF--
array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(3) "Foo"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(3) "Bar"
}
}
12 changes: 12 additions & 0 deletions Zend/tests/attributes/constants/multiple_constants_error.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Error trying to add attributes to multiple constants at once
--FILE--
<?php

#[\Foo]
const First = 1,
Second = 2;

?>
--EXPECTF--
Fatal error: Cannot apply attributes to multiple constants at once in %s on line %d
11 changes: 11 additions & 0 deletions Zend/tests/attributes/constants/must_target_const-internal.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Error when attribute does not target constants (internal attribute)
--FILE--
<?php

#[Attribute]
const EXAMPLE = 'Foo';

?>
--EXPECTF--
Fatal error: Attribute "Attribute" cannot target constant (allowed targets: class) in %s on line %d
Loading