Skip to content

Commit

Permalink
[RFC] Add support for attributes on compile-time constants
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielEScherzer committed Jan 4, 2025
1 parent ac8b7b8 commit 819ed7e
Show file tree
Hide file tree
Showing 62 changed files with 1,853 additions and 583 deletions.
6 changes: 6 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ PHP 8.5 UPGRADE NOTES
- Core:
. Added support for Closures in constant expressions.
RFC: https://wiki.php.net/rfc/closures_in_const_expr
. Added support for attributes on compile-time non-class constants.
. Added constant Attribute::TARGET_CONSTANT.
. The #[\Deprecated] attribute can now be used on constants.
RFC: https://wiki.php.net/rfc/attributes-on-constants

- DOM:
. Added Dom\Element::$outerHTML.
Expand Down Expand Up @@ -157,6 +161,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)
26 changes: 26 additions & 0 deletions Zend/tests/attributes/constants/allow_named_parameters.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Verify that named parameters can be passed to attributes on constants
--FILE--
<?php

#[MyAttribute(foo: "bar")]
const EXAMPLE = 'Foo';

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

?>
--EXPECTF--
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(11) "MyAttribute"
}
}
array(1) {
["foo"]=>
string(3) "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

0 comments on commit 819ed7e

Please sign in to comment.