From 8597a970799663c7b8df75b1f2330958d2b67e3e Mon Sep 17 00:00:00 2001 From: Theodore Brown Date: Thu, 2 Jul 2020 09:56:21 -0600 Subject: [PATCH] Implement Shorter Attribute Syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RFC: https://wiki.php.net/rfc/shorter_attribute_syntax Co-authored-by: Martin Schröder --- UPGRADING | 5 +++ Zend/tests/attributes/001_placement.phpt | 18 ++++----- Zend/tests/attributes/002_rfcexample.phpt | 4 +- Zend/tests/attributes/003_ast_nodes.phpt | 12 +++--- .../tests/attributes/004_name_resolution.phpt | 10 ++--- Zend/tests/attributes/005_objects.phpt | 20 +++++----- Zend/tests/attributes/006_filter.phpt | 16 ++++---- .../attributes/008_wrong_attribution.phpt | 2 +- .../009_doctrine_annotations_example.phpt | 22 ++++++----- .../010_unsupported_const_expression.phpt | 2 +- Zend/tests/attributes/011_inheritance.phpt | 8 ++-- Zend/tests/attributes/012_ast_export.phpt | 38 +++++++++---------- Zend/tests/attributes/013_class_scope.phpt | 16 ++++---- .../attributes/014_class_const_group.phpt | 2 +- Zend/tests/attributes/015_property_group.phpt | 2 +- ...t => 016_custom_attribute_validation.phpt} | 6 +-- Zend/tests/attributes/017_closure_scope.phpt | 2 +- .../018_fatal_error_in_argument.phpt | 2 +- .../019_variable_attribute_name.phpt | 2 +- .../020_userland_attribute_validation.phpt | 12 +++--- ...021_attribute_flags_type_is_validated.phpt | 2 +- ...22_attribute_flags_value_is_validated.phpt | 2 +- .../023_ast_node_in_validation.phpt | 2 +- .../024_internal_target_validation.phpt | 2 +- .../025_internal_repeatable_validation.phpt | 4 +- Zend/tests/attributes/026_unpack_in_args.phpt | 2 +- .../attributes/027_trailing_comma_args.phpt | 4 +- Zend/tests/ctor_promotion_attributes.phpt | 2 +- Zend/zend_ast.c | 4 +- Zend/zend_language_parser.y | 3 +- Zend/zend_language_scanner.l | 4 ++ ext/tokenizer/tests/attributes.phpt | 20 ++++++++++ ext/tokenizer/tokenizer_data.c | 2 + ext/zend_test/test.c | 2 +- 34 files changed, 144 insertions(+), 112 deletions(-) rename Zend/tests/attributes/{016_target_resolution_compiler_attributes.phpt => 016_custom_attribute_validation.phpt} (50%) create mode 100644 ext/tokenizer/tests/attributes.phpt diff --git a/UPGRADING b/UPGRADING index f7c6ec0a147ed..2e2b694b8d111 100644 --- a/UPGRADING +++ b/UPGRADING @@ -78,6 +78,10 @@ PHP 8.0 UPGRADE NOTES Additionally, care should be taken that error messages are not displayed in production environments, which can result in information leaks. Please ensure that display_errors=Off is used in conjunction with error logging. + . Adding more than one @ operator to an expression is no longer supported, + since this syntax is now used for attributes (previously extra @ operators + had no effect). + RFC: https://wiki.php.net/rfc/shorter_attribute_syntax . Inheritance errors due to incompatible method signatures (LSP violations) will now always generate a fatal error. Previously a warning was generated in some cases. @@ -605,6 +609,7 @@ PHP 8.0 UPGRADE NOTES . Added support for Attributes RFC: https://wiki.php.net/rfc/attributes_v2 RFC: https://wiki.php.net/rfc/attribute_amendments + RFC: https://wiki.php.net/rfc/shorter_attribute_syntax . Added support for constructor property promotion (declaring properties in the constructor signature). RFC: https://wiki.php.net/rfc/constructor_promotion diff --git a/Zend/tests/attributes/001_placement.phpt b/Zend/tests/attributes/001_placement.phpt index cf7bcd450406d..9bf9c4d2f761f 100644 --- a/Zend/tests/attributes/001_placement.phpt +++ b/Zend/tests/attributes/001_placement.phpt @@ -3,27 +3,27 @@ Attributes can be placed on all supported elements. --FILE-- > +@@A1(1) class Foo { - <> + @@A1(2) public const FOO = 'foo'; - <> + @@A1(3) public $x; - <> - public function foo(<> $a, <> $b) { } + @@A1(4) + public function foo(@@A1(5) $a, @@A1(6) $b) { } } -$object = new <> class () { }; +$object = new @@A1(7) class () { }; -<> +@@A1(8) function f1() { } -$f2 = <> function () { }; +$f2 = @@A1(9) function () { }; -$f3 = <> fn () => 1; +$f3 = @@A1(10) fn () => 1; $ref = new \ReflectionClass(Foo::class); diff --git a/Zend/tests/attributes/002_rfcexample.phpt b/Zend/tests/attributes/002_rfcexample.phpt index 6d1028436dace..bc608c3eed0d9 100644 --- a/Zend/tests/attributes/002_rfcexample.phpt +++ b/Zend/tests/attributes/002_rfcexample.phpt @@ -6,7 +6,7 @@ Attributes: Example from Attributes RFC namespace My\Attributes { use Attribute; - <> + @@Attribute class SingleArgument { public $argumentValue; @@ -19,7 +19,7 @@ namespace My\Attributes { namespace { use My\Attributes\SingleArgument; - <> + @@SingleArgument("Hello World") class Foo { } diff --git a/Zend/tests/attributes/003_ast_nodes.phpt b/Zend/tests/attributes/003_ast_nodes.phpt index afdab838f84b5..21125bde13a04 100644 --- a/Zend/tests/attributes/003_ast_nodes.phpt +++ b/Zend/tests/attributes/003_ast_nodes.phpt @@ -5,7 +5,7 @@ Attributes can deal with AST nodes. define('V1', strtoupper(php_sapi_name())); -< V1])>> +@@A1([V1 => V1]) class C1 { public const BAR = 'bar'; @@ -20,7 +20,7 @@ var_dump(count($args), $args[0][V1] === V1); echo "\n"; -<> +@@A1(V1, 1 + 2, C1::class) class C2 { } $ref = new \ReflectionClass(C2::class); @@ -35,7 +35,7 @@ var_dump($args[2] === C1::class); echo "\n"; -<> +@@A1(self::FOO, C1::BAR) class C3 { private const FOO = 'foo'; @@ -52,20 +52,20 @@ var_dump($args[1] === C1::BAR); echo "\n"; -<> 1)>> +@@ExampleWithShift(4 >> 1) class C4 {} $ref = new \ReflectionClass(C4::class); var_dump($ref->getAttributes()[0]->getArguments()); echo "\n"; -<> +@@Attribute class C5 { public function __construct() { } } -$ref = new \ReflectionFunction(<> function () { }); +$ref = new \ReflectionFunction(@@C5(MissingClass::SOME_CONST) function () { }); $attr = $ref->getAttributes(); var_dump(count($attr)); diff --git a/Zend/tests/attributes/004_name_resolution.phpt b/Zend/tests/attributes/004_name_resolution.phpt index 30537e2c12a4e..3c0e1190ffec9 100644 --- a/Zend/tests/attributes/004_name_resolution.phpt +++ b/Zend/tests/attributes/004_name_resolution.phpt @@ -25,11 +25,11 @@ namespace Foo { use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Attributes; - <> - <> - <<\Doctrine\ORM\Mapping\Entity("absolute from namespace")>> - <<\Entity("import absolute from global")>> - <> + @@Entity("imported class") + @@ORM\Entity("imported namespace") + @@\Doctrine\ORM\Mapping\Entity("absolute from namespace") + @@\Entity("import absolute from global") + @@Attributes\Table() function foo() { } } diff --git a/Zend/tests/attributes/005_objects.phpt b/Zend/tests/attributes/005_objects.phpt index 712ec93beaf4f..83f523182b1b5 100644 --- a/Zend/tests/attributes/005_objects.phpt +++ b/Zend/tests/attributes/005_objects.phpt @@ -3,7 +3,7 @@ Attributes can be converted into objects. --FILE-- > +@@Attribute(Attribute::TARGET_FUNCTION) class A1 { public string $name; @@ -16,7 +16,7 @@ class A1 } } -$ref = new \ReflectionFunction(<> function () { }); +$ref = new \ReflectionFunction(@@A1('test') function () { }); foreach ($ref->getAttributes() as $attr) { $obj = $attr->newInstance(); @@ -26,7 +26,7 @@ foreach ($ref->getAttributes() as $attr) { echo "\n"; -$ref = new \ReflectionFunction(<> function () { }); +$ref = new \ReflectionFunction(@@A1 function () { }); try { $ref->getAttributes()[0]->newInstance(); @@ -36,7 +36,7 @@ try { echo "\n"; -$ref = new \ReflectionFunction(<> function () { }); +$ref = new \ReflectionFunction(@@A1([]) function () { }); try { $ref->getAttributes()[0]->newInstance(); @@ -46,7 +46,7 @@ try { echo "\n"; -$ref = new \ReflectionFunction(<> function () { }); +$ref = new \ReflectionFunction(@@A2 function () { }); try { $ref->getAttributes()[0]->newInstance(); @@ -56,13 +56,13 @@ try { echo "\n"; -<> +@@Attribute class A3 { private function __construct() { } } -$ref = new \ReflectionFunction(<> function () { }); +$ref = new \ReflectionFunction(@@A3 function () { }); try { $ref->getAttributes()[0]->newInstance(); @@ -72,10 +72,10 @@ try { echo "\n"; -<> +@@Attribute class A4 { } -$ref = new \ReflectionFunction(<> function () { }); +$ref = new \ReflectionFunction(@@A4(1) function () { }); try { $ref->getAttributes()[0]->newInstance(); @@ -87,7 +87,7 @@ echo "\n"; class A5 { } -$ref = new \ReflectionFunction(<> function () { }); +$ref = new \ReflectionFunction(@@A5 function () { }); try { $ref->getAttributes()[0]->newInstance(); diff --git a/Zend/tests/attributes/006_filter.phpt b/Zend/tests/attributes/006_filter.phpt index 49c02d6e35017..69ee146b49f03 100644 --- a/Zend/tests/attributes/006_filter.phpt +++ b/Zend/tests/attributes/006_filter.phpt @@ -3,17 +3,17 @@ Attributes can be filtered by name and base type. --FILE-- > <> function () { }); +$ref = new \ReflectionFunction(@@A1 @@A2 function () { }); $attr = $ref->getAttributes(A3::class); var_dump(count($attr)); -$ref = new \ReflectionFunction(<> <> function () { }); +$ref = new \ReflectionFunction(@@A1 @@A2 function () { }); $attr = $ref->getAttributes(A2::class); var_dump(count($attr), $attr[0]->getName()); -$ref = new \ReflectionFunction(<> <> <> function () { }); +$ref = new \ReflectionFunction(@@A1 @@A2 @@A2 function () { }); $attr = $ref->getAttributes(A2::class); var_dump(count($attr), $attr[0]->getName(), $attr[1]->getName()); @@ -25,27 +25,27 @@ class A1 implements Base { } class A2 implements Base { } class A3 extends A2 { } -$ref = new \ReflectionFunction(<> <> <> function () { }); +$ref = new \ReflectionFunction(@@A1 @@A2 @@A5 function () { }); $attr = $ref->getAttributes(\stdClass::class, \ReflectionAttribute::IS_INSTANCEOF); var_dump(count($attr)); print_r(array_map(fn ($a) => $a->getName(), $attr)); -$ref = new \ReflectionFunction(<> <> function () { }); +$ref = new \ReflectionFunction(@@A1 @@A2 function () { }); $attr = $ref->getAttributes(A1::class, \ReflectionAttribute::IS_INSTANCEOF); var_dump(count($attr)); print_r(array_map(fn ($a) => $a->getName(), $attr)); -$ref = new \ReflectionFunction(<> <> function () { }); +$ref = new \ReflectionFunction(@@A1 @@A2 function () { }); $attr = $ref->getAttributes(Base::class, \ReflectionAttribute::IS_INSTANCEOF); var_dump(count($attr)); print_r(array_map(fn ($a) => $a->getName(), $attr)); -$ref = new \ReflectionFunction(<> <> <> function () { }); +$ref = new \ReflectionFunction(@@A1 @@A2 @@A3 function () { }); $attr = $ref->getAttributes(A2::class, \ReflectionAttribute::IS_INSTANCEOF); var_dump(count($attr)); print_r(array_map(fn ($a) => $a->getName(), $attr)); -$ref = new \ReflectionFunction(<> <> <> function () { }); +$ref = new \ReflectionFunction(@@A1 @@A2 @@A3 function () { }); $attr = $ref->getAttributes(Base::class, \ReflectionAttribute::IS_INSTANCEOF); var_dump(count($attr)); print_r(array_map(fn ($a) => $a->getName(), $attr)); diff --git a/Zend/tests/attributes/008_wrong_attribution.phpt b/Zend/tests/attributes/008_wrong_attribution.phpt index 20a800b9a70f2..21ea9783d1c26 100644 --- a/Zend/tests/attributes/008_wrong_attribution.phpt +++ b/Zend/tests/attributes/008_wrong_attribution.phpt @@ -3,7 +3,7 @@ Attributes: Prevent Attribute on non classes --FILE-- > +@@Attribute function foo() {} --EXPECTF-- Fatal error: Attribute "Attribute" cannot target function (allowed targets: class) in %s diff --git a/Zend/tests/attributes/009_doctrine_annotations_example.phpt b/Zend/tests/attributes/009_doctrine_annotations_example.phpt index 38fc6389e070c..51cb315d487c9 100644 --- a/Zend/tests/attributes/009_doctrine_annotations_example.phpt +++ b/Zend/tests/attributes/009_doctrine_annotations_example.phpt @@ -25,20 +25,22 @@ namespace { use Doctrine\ORM\Attributes as ORM; use Symfony\Component\Validator\Constraints as Assert; -<> +@@ORM\Entity /** @ORM\Entity */ class User { /** @ORM\Id @ORM\Column(type="integer"*) @ORM\GeneratedValue */ - <><><> + @@ORM\Id + @@ORM\Column("integer") + @@ORM\GeneratedValue private $id; /** * @ORM\Column(type="string", unique=true) * @Assert\Email(message="The email '{{ value }}' is not a valid email.") */ - <> - < "The email '{{ value }}' is not a valid email."))>> + @@ORM\Column("string", ORM\Column::UNIQUE) + @@Assert\Email(array("message" => "The email '{{ value }}' is not a valid email.")) private $email; /** @@ -50,8 +52,8 @@ class User * maxMessage = "You cannot be taller than {{ limit }}cm to enter" * ) */ - < 120, "max" => 180, "minMessage" => "You must be at least {{ limit }}cm tall to enter"])>> - <> + @@Assert\Range(["min" => 120, "max" => 180, "minMessage" => "You must be at least {{ limit }}cm tall to enter"]) + @@ORM\Column(ORM\Column::T_INTEGER) protected $height; /** @@ -61,10 +63,10 @@ class User * inverseJoinColumns={@ORM\JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)} * ) */ - <> - <> - <> - <> + @@ORM\ManyToMany(Phonenumber::class) + @@ORM\JoinTable("users_phonenumbers") + @@ORM\JoinColumn("user_id", "id") + @@ORM\InverseJoinColumn("phonenumber_id", "id", ORM\JoinColumn::UNIQUE) private $phonenumbers; } diff --git a/Zend/tests/attributes/010_unsupported_const_expression.phpt b/Zend/tests/attributes/010_unsupported_const_expression.phpt index 50afa6c4f65c5..fb30e2c486bd5 100644 --- a/Zend/tests/attributes/010_unsupported_const_expression.phpt +++ b/Zend/tests/attributes/010_unsupported_const_expression.phpt @@ -3,7 +3,7 @@ Attribute arguments support only const expressions. --FILE-- > +@@A1(foo()) class C1 { } ?> diff --git a/Zend/tests/attributes/011_inheritance.phpt b/Zend/tests/attributes/011_inheritance.phpt index 007cd5991e7d7..25c943dea30e9 100644 --- a/Zend/tests/attributes/011_inheritance.phpt +++ b/Zend/tests/attributes/011_inheritance.phpt @@ -3,10 +3,10 @@ Attributes comply with inheritance rules. --FILE-- > +@@A2 class C1 { - <> + @@A1 public function foo() { } } @@ -17,7 +17,7 @@ class C2 extends C1 class C3 extends C1 { - <> + @@A1 public function bar() { } } @@ -37,7 +37,7 @@ echo "\n"; trait T1 { - <> + @@A2 public $a; } diff --git a/Zend/tests/attributes/012_ast_export.phpt b/Zend/tests/attributes/012_ast_export.phpt index fe3539c8a9fb8..c811327d0cd9b 100644 --- a/Zend/tests/attributes/012_ast_export.phpt +++ b/Zend/tests/attributes/012_ast_export.phpt @@ -3,51 +3,51 @@ Attributes AST can be exported. --FILE-- ><> function ($a, <> $b) { })); +assert(0 && ($a = @@A1 @@A2 function ($a, @@A3(1) $b) { })); -assert(0 && ($a = <> fn () => 1)); +assert(0 && ($a = @@A1(1, 2, 1 + 2) fn () => 1)); -assert(0 && ($a = new <> class() { - <><> const FOO = 'foo'; - <> public $x; - <> function a() { } +assert(0 && ($a = new @@A1 class() { + @@A1@@A2 const FOO = 'foo'; + @@A2 public $x; + @@A3 function a() { } })); assert(0 && ($a = function () { - <> class Test1 { } - <> interface Test2 { } - <> trait Test3 { } + @@A1 class Test1 { } + @@A2 interface Test2 { } + @@A3 trait Test3 { } })); ?> --EXPECTF-- -Warning: assert(): assert(0 && ($a = <> <> function ($a, <> $b) { +Warning: assert(): assert(0 && ($a = @@A1 @@A2 function ($a, @@A3(1) $b) { })) failed in %s on line %d -Warning: assert(): assert(0 && ($a = <> fn() => 1)) failed in %s on line %d +Warning: assert(): assert(0 && ($a = @@A1(1, 2, 1 + 2) fn() => 1)) failed in %s on line %d -Warning: assert(): assert(0 && ($a = new <> class { - <> - <> +Warning: assert(): assert(0 && ($a = new @@A1 class { + @@A1 + @@A2 public const FOO = 'foo'; - <> + @@A2 public $x; - <> + @@A3 public function a() { } })) failed in %s on line %d Warning: assert(): assert(0 && ($a = function () { - <> + @@A1 class Test1 { } - <> + @@A2 interface Test2 { } - <> + @@A3 trait Test3 { } diff --git a/Zend/tests/attributes/013_class_scope.phpt b/Zend/tests/attributes/013_class_scope.phpt index 94764c2d69088..be6c7a60da4f4 100644 --- a/Zend/tests/attributes/013_class_scope.phpt +++ b/Zend/tests/attributes/013_class_scope.phpt @@ -3,17 +3,17 @@ Attributes make use of class scope. --FILE-- > +@@A1(self::class, self::FOO) class C1 { - <> + @@A1(self::class, self::FOO) private const FOO = 'foo'; - <> + @@A1(self::class, self::FOO) public $a; - <> - public function bar(<> $p) { } + @@A1(self::class, self::FOO) + public function bar(@@A1(self::class, self::FOO) $p) { } } $ref = new \ReflectionClass(C1::class); @@ -27,7 +27,7 @@ echo "\n"; trait T1 { - <> + @@A1(self::class, self::FOO) public function foo() { } } @@ -58,10 +58,10 @@ class C3 public static function foo() { - return new <> class() { + return new @@A1(self::class, self::FOO) class() { private const FOO = 'bar'; - <> + @@A1(self::class, self::FOO) public function bar() { } }; } diff --git a/Zend/tests/attributes/014_class_const_group.phpt b/Zend/tests/attributes/014_class_const_group.phpt index 18ff93683aff9..5da5c52a95779 100644 --- a/Zend/tests/attributes/014_class_const_group.phpt +++ b/Zend/tests/attributes/014_class_const_group.phpt @@ -5,7 +5,7 @@ Attributes cannot be applied to groups of class constants. class C1 { - <> + @@A1 public const A = 1, B = 2; } diff --git a/Zend/tests/attributes/015_property_group.phpt b/Zend/tests/attributes/015_property_group.phpt index 493e4ae260136..52e3b0a92a8d7 100644 --- a/Zend/tests/attributes/015_property_group.phpt +++ b/Zend/tests/attributes/015_property_group.phpt @@ -5,7 +5,7 @@ Attributes cannot be applied to groups of properties. class C1 { - <> + @@A1 public $x, $y; } diff --git a/Zend/tests/attributes/016_target_resolution_compiler_attributes.phpt b/Zend/tests/attributes/016_custom_attribute_validation.phpt similarity index 50% rename from Zend/tests/attributes/016_target_resolution_compiler_attributes.phpt rename to Zend/tests/attributes/016_custom_attribute_validation.phpt index c96b49cbd7fc1..6095d390d9963 100644 --- a/Zend/tests/attributes/016_target_resolution_compiler_attributes.phpt +++ b/Zend/tests/attributes/016_custom_attribute_validation.phpt @@ -1,5 +1,5 @@ --TEST-- -Attributes: Compiler Attributes can check for target declarations +Attribute validation callback of internal attributes. --SKIPIF-- > +@@ZendTestAttribute function foo() { } --EXPECTF-- -Fatal error: Only classes can be marked with <> in %s +Fatal error: Only classes can be marked with @@ZendTestAttribute in %s diff --git a/Zend/tests/attributes/017_closure_scope.phpt b/Zend/tests/attributes/017_closure_scope.phpt index f57b4b73b3c08..936a8ec338a74 100644 --- a/Zend/tests/attributes/017_closure_scope.phpt +++ b/Zend/tests/attributes/017_closure_scope.phpt @@ -14,7 +14,7 @@ class C1 public static function foo() { - return <> function (<> $p) { }; + return @@A1(self::class, self::FOO) function (@@A1(self::class, self::FOO) $p) { }; } } diff --git a/Zend/tests/attributes/018_fatal_error_in_argument.phpt b/Zend/tests/attributes/018_fatal_error_in_argument.phpt index a950c8658ab19..db2719d85a92c 100644 --- a/Zend/tests/attributes/018_fatal_error_in_argument.phpt +++ b/Zend/tests/attributes/018_fatal_error_in_argument.phpt @@ -3,7 +3,7 @@ Don't free uninitialized memory if a fatal error occurs in an attribute argument --FILE-- b::c)>> +@@Attr(a->b::c) function test() {} ?> diff --git a/Zend/tests/attributes/019_variable_attribute_name.phpt b/Zend/tests/attributes/019_variable_attribute_name.phpt index a259c06f3fe77..9b8a3c22116df 100644 --- a/Zend/tests/attributes/019_variable_attribute_name.phpt +++ b/Zend/tests/attributes/019_variable_attribute_name.phpt @@ -3,7 +3,7 @@ Attribute name cannot be a variable --FILE-- > +@@$x class A {} ?> diff --git a/Zend/tests/attributes/020_userland_attribute_validation.phpt b/Zend/tests/attributes/020_userland_attribute_validation.phpt index 48c5e2651bdf9..1025b5008e202 100644 --- a/Zend/tests/attributes/020_userland_attribute_validation.phpt +++ b/Zend/tests/attributes/020_userland_attribute_validation.phpt @@ -3,17 +3,17 @@ Attributes expose and verify target and repeatable data. --FILE-- > +@@Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD) class A1 { } -$ref = new \ReflectionFunction(<> function () { }); +$ref = new \ReflectionFunction(@@A1 function () { }); $attr = $ref->getAttributes()[0]; var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_FUNCTION, $attr->isRepeated()); var_dump(get_class($attr->newInstance())); echo "\n"; -$ref = new \ReflectionObject(new <> class() { }); +$ref = new \ReflectionObject(new @@A1 class() { }); $attr = $ref->getAttributes()[0]; var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_CLASS, $attr->isRepeated()); @@ -25,7 +25,7 @@ try { echo "\n"; -$ref = new \ReflectionFunction(<> <> function () { }); +$ref = new \ReflectionFunction(@@A1 @@A1 function () { }); $attr = $ref->getAttributes()[0]; var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_FUNCTION, $attr->isRepeated()); @@ -37,10 +37,10 @@ try { echo "\n"; -<> +@@Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE) class A2 { } -$ref = new \ReflectionObject(new <> <> class() { }); +$ref = new \ReflectionObject(new @@A2 @@A2 class() { }); $attr = $ref->getAttributes()[0]; var_dump($attr->getName(), $attr->getTarget() == Attribute::TARGET_CLASS, $attr->isRepeated()); var_dump(get_class($attr->newInstance())); diff --git a/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt b/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt index 06ed4d08fda47..0f0b915ebe762 100644 --- a/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt +++ b/Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt @@ -3,7 +3,7 @@ Attribute flags type is validated. --FILE-- > +@@Attribute("foo") class A1 { } ?> diff --git a/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt b/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt index 1deb81e4d5022..28ad550385d4f 100644 --- a/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt +++ b/Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt @@ -3,7 +3,7 @@ Attribute flags value is validated. --FILE-- > +@@Attribute(-1) class A1 { } ?> diff --git a/Zend/tests/attributes/023_ast_node_in_validation.phpt b/Zend/tests/attributes/023_ast_node_in_validation.phpt index 57a7287cae280..ce44527f62fd5 100644 --- a/Zend/tests/attributes/023_ast_node_in_validation.phpt +++ b/Zend/tests/attributes/023_ast_node_in_validation.phpt @@ -3,7 +3,7 @@ Attribute flags value is validated. --FILE-- > +@@Attribute(Foo::BAR) class A1 { } ?> diff --git a/Zend/tests/attributes/024_internal_target_validation.phpt b/Zend/tests/attributes/024_internal_target_validation.phpt index 746ceb3c697eb..49a5ae68c8b14 100644 --- a/Zend/tests/attributes/024_internal_target_validation.phpt +++ b/Zend/tests/attributes/024_internal_target_validation.phpt @@ -3,7 +3,7 @@ Internal attribute targets are validated. --FILE-- > +@@Attribute function a1() { } ?> diff --git a/Zend/tests/attributes/025_internal_repeatable_validation.phpt b/Zend/tests/attributes/025_internal_repeatable_validation.phpt index 631f0b5054c80..b3c83e810f2af 100644 --- a/Zend/tests/attributes/025_internal_repeatable_validation.phpt +++ b/Zend/tests/attributes/025_internal_repeatable_validation.phpt @@ -3,8 +3,8 @@ Internal attribute targets are validated. --FILE-- > -<> +@@Attribute +@@Attribute class A1 { } ?> diff --git a/Zend/tests/attributes/026_unpack_in_args.phpt b/Zend/tests/attributes/026_unpack_in_args.phpt index 04a038d3e58a7..37f8fb5679aba 100644 --- a/Zend/tests/attributes/026_unpack_in_args.phpt +++ b/Zend/tests/attributes/026_unpack_in_args.phpt @@ -3,7 +3,7 @@ Cannot use unpacking in attribute argument list --FILE-- > +@@MyAttribute(...[1, 2, 3]) class Foo { } ?> diff --git a/Zend/tests/attributes/027_trailing_comma_args.phpt b/Zend/tests/attributes/027_trailing_comma_args.phpt index c8f6adf0e83ee..5ac47e08a8586 100644 --- a/Zend/tests/attributes/027_trailing_comma_args.phpt +++ b/Zend/tests/attributes/027_trailing_comma_args.phpt @@ -3,12 +3,12 @@ Trailing comma in attribute argument list --FILE-- > +) class Foo { } $ref = new \ReflectionClass(Foo::class); diff --git a/Zend/tests/ctor_promotion_attributes.phpt b/Zend/tests/ctor_promotion_attributes.phpt index d85d8d3181d6f..08c7117dafb4d 100644 --- a/Zend/tests/ctor_promotion_attributes.phpt +++ b/Zend/tests/ctor_promotion_attributes.phpt @@ -5,7 +5,7 @@ Attributes on promoted properties are assigned to both the property and paramete class Test { public function __construct( - <> + @@NonNegative public int $num, ) {} } diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index b1e564b5e90dd..42ef04cdf44fa 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1356,7 +1356,7 @@ static ZEND_COLD void zend_ast_export_attributes(smart_str *str, zend_ast *ast, for (i = 0; i < list->children; i++) { zend_ast *attr = list->child[i]; - smart_str_appends(str, "<<"); + smart_str_appends(str, "@@"); zend_ast_export_ns_name(str, attr->child[0], 0, indent); if (attr->child[1]) { @@ -1373,8 +1373,6 @@ static ZEND_COLD void zend_ast_export_attributes(smart_str *str, zend_ast *ast, smart_str_appendc(str, ')'); } - smart_str_appends(str, ">>"); - if (newlines) { smart_str_appendc(str, '\n'); zend_ast_export_indent(str, indent); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index f8b4b6d862882..55f2c0f49cf18 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -178,6 +178,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_NS_C "'__NAMESPACE__'" %token END 0 "end of file" +%token T_ATTRIBUTE "'@@'" %token T_PLUS_EQUAL "'+='" %token T_MINUS_EQUAL "'-='" %token T_MUL_EQUAL "'*='" @@ -345,7 +346,7 @@ attribute_decl: ; attribute: - T_SL attribute_decl T_SR { $$ = $2; } + T_ATTRIBUTE attribute_decl { $$ = $2; } ; attributes: diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 3b6cc1dd676c7..7f42159b46698 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1422,6 +1422,10 @@ NEWLINE ("\r"|"\n"|"\r\n") RETURN_TOKEN_WITH_IDENT(T_RETURN); } +"@@" { + RETURN_TOKEN(T_ATTRIBUTE); +} + "yield"{WHITESPACE}"from"[^a-zA-Z0-9_\x80-\xff] { yyless(yyleng - 1); HANDLE_NEWLINES(yytext, yyleng); diff --git a/ext/tokenizer/tests/attributes.phpt b/ext/tokenizer/tests/attributes.phpt new file mode 100644 index 0000000000000..8795a4c5dcddf --- /dev/null +++ b/ext/tokenizer/tests/attributes.phpt @@ -0,0 +1,20 @@ +--TEST-- +Attributes are exposed as tokens. +--FILE-- + +--EXPECT-- +string(11) "T_ATTRIBUTE" +bool(true) +string(2) "@@" +string(2) "A1" diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 049433f4b7b1a..a2a367181655a 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -81,6 +81,7 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_NAME_RELATIVE", T_NAME_RELATIVE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NAME_QUALIFIED", T_NAME_QUALIFIED, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_VARIABLE", T_VARIABLE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_ATTRIBUTE", T_ATTRIBUTE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INLINE_HTML", T_INLINE_HTML, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ENCAPSED_AND_WHITESPACE", T_ENCAPSED_AND_WHITESPACE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_CONSTANT_ENCAPSED_STRING", T_CONSTANT_ENCAPSED_STRING, CONST_CS | CONST_PERSISTENT); @@ -229,6 +230,7 @@ char *get_token_type_name(int token_type) case T_NAME_RELATIVE: return "T_NAME_RELATIVE"; case T_NAME_QUALIFIED: return "T_NAME_QUALIFIED"; case T_VARIABLE: return "T_VARIABLE"; + case T_ATTRIBUTE: return "T_ATTRIBUTE"; case T_INLINE_HTML: return "T_INLINE_HTML"; case T_ENCAPSED_AND_WHITESPACE: return "T_ENCAPSED_AND_WHITESPACE"; case T_CONSTANT_ENCAPSED_STRING: return "T_CONSTANT_ENCAPSED_STRING"; diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 413a6a302f2af..900c70f59d5af 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -257,7 +257,7 @@ static zend_function *zend_test_class_static_method_get(zend_class_entry *ce, ze void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope) { if (target != ZEND_ATTRIBUTE_TARGET_CLASS) { - zend_error(E_COMPILE_ERROR, "Only classes can be marked with <>"); + zend_error(E_COMPILE_ERROR, "Only classes can be marked with @@ZendTestAttribute"); } }