]> granicus.if.org Git - php/commitdiff
Implement Shorter Attribute Syntax
authorTheodore Brown <theodorejb@outlook.com>
Thu, 2 Jul 2020 15:56:21 +0000 (09:56 -0600)
committerNikita Popov <nikita.ppv@gmail.com>
Tue, 28 Jul 2020 13:28:57 +0000 (15:28 +0200)
RFC: https://wiki.php.net/rfc/shorter_attribute_syntax

Closes GH-5796.

Co-authored-by: Martin Schröder <m.schroeder2007@gmail.com>
34 files changed:
UPGRADING
Zend/tests/attributes/001_placement.phpt
Zend/tests/attributes/002_rfcexample.phpt
Zend/tests/attributes/003_ast_nodes.phpt
Zend/tests/attributes/004_name_resolution.phpt
Zend/tests/attributes/005_objects.phpt
Zend/tests/attributes/006_filter.phpt
Zend/tests/attributes/008_wrong_attribution.phpt
Zend/tests/attributes/009_doctrine_annotations_example.phpt
Zend/tests/attributes/010_unsupported_const_expression.phpt
Zend/tests/attributes/011_inheritance.phpt
Zend/tests/attributes/012_ast_export.phpt
Zend/tests/attributes/013_class_scope.phpt
Zend/tests/attributes/014_class_const_group.phpt
Zend/tests/attributes/015_property_group.phpt
Zend/tests/attributes/016_custom_attribute_validation.phpt [moved from Zend/tests/attributes/016_target_resolution_compiler_attributes.phpt with 50% similarity]
Zend/tests/attributes/017_closure_scope.phpt
Zend/tests/attributes/018_fatal_error_in_argument.phpt
Zend/tests/attributes/019_variable_attribute_name.phpt
Zend/tests/attributes/020_userland_attribute_validation.phpt
Zend/tests/attributes/021_attribute_flags_type_is_validated.phpt
Zend/tests/attributes/022_attribute_flags_value_is_validated.phpt
Zend/tests/attributes/023_ast_node_in_validation.phpt
Zend/tests/attributes/024_internal_target_validation.phpt
Zend/tests/attributes/025_internal_repeatable_validation.phpt
Zend/tests/attributes/026_unpack_in_args.phpt
Zend/tests/attributes/027_trailing_comma_args.phpt
Zend/tests/ctor_promotion_attributes.phpt
Zend/zend_ast.c
Zend/zend_language_parser.y
Zend/zend_language_scanner.l
ext/tokenizer/tests/attributes.phpt [new file with mode: 0644]
ext/tokenizer/tokenizer_data.c
ext/zend_test/test.c

index 7a9c92ee65f45df4c31bca3efc792b13c4f8804b..6eca850664d91cab7f403e634b35d24eb5e9a133 100644 (file)
--- 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
index cf7bcd450406d450078a4fba0149a4ac5b340fbc..9bf9c4d2f761f0d450d77a7b0502504032b8eed9 100644 (file)
@@ -3,27 +3,27 @@ Attributes can be placed on all supported elements.
 --FILE--
 <?php
 
-<<A1(1)>>
+@@A1(1)
 class Foo
 {
-    <<A1(2)>>
+    @@A1(2)
     public const FOO = 'foo';
     
-    <<A1(3)>>
+    @@A1(3)
     public $x;
     
-    <<A1(4)>>
-    public function foo(<<A1(5)>> $a, <<A1(6)>> $b) { }
+    @@A1(4)
+    public function foo(@@A1(5) $a, @@A1(6) $b) { }
 }
 
-$object = new <<A1(7)>> class () { };
+$object = new @@A1(7) class () { };
 
-<<A1(8)>>
+@@A1(8)
 function f1() { }
 
-$f2 = <<A1(9)>> function () { };
+$f2 = @@A1(9) function () { };
 
-$f3 = <<A1(10)>> fn () => 1;
+$f3 = @@A1(10) fn () => 1;
 
 $ref = new \ReflectionClass(Foo::class);
 
index 6d1028436daceb77dbf392e8097e9b50f17318e6..bc608c3eed0d98f6515fe6be4e839526a4485bdf 100644 (file)
@@ -6,7 +6,7 @@ Attributes: Example from Attributes RFC
 namespace My\Attributes {
     use Attribute;
 
-    <<Attribute>>
+    @@Attribute
     class SingleArgument {
         public $argumentValue;
 
@@ -19,7 +19,7 @@ namespace My\Attributes {
 namespace {
     use My\Attributes\SingleArgument;
 
-    <<SingleArgument("Hello World")>>
+    @@SingleArgument("Hello World")
     class Foo {
     }
 
index afdab838f84b58808e5252a3c461aa17463cc4ce..21125bde13a04f8fe488493ff56d8612b0e8984c 100644 (file)
@@ -5,7 +5,7 @@ Attributes can deal with AST nodes.
 
 define('V1', strtoupper(php_sapi_name()));
 
-<<A1([V1 => 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)>>
+@@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)>>
+@@A1(self::FOO, C1::BAR)
 class C3
 {
        private const FOO = 'foo';
@@ -52,20 +52,20 @@ var_dump($args[1] === C1::BAR);
 
 echo "\n";
 
-<<ExampleWithShift(4 >> 1)>>
+@@ExampleWithShift(4 >> 1)
 class C4 {}
 $ref = new \ReflectionClass(C4::class);
 var_dump($ref->getAttributes()[0]->getArguments());
 
 echo "\n";
 
-<<Attribute>>
+@@Attribute
 class C5
 {
        public function __construct() { }
 }
 
-$ref = new \ReflectionFunction(<<C5(MissingClass::SOME_CONST)>> function () { });
+$ref = new \ReflectionFunction(@@C5(MissingClass::SOME_CONST) function () { });
 $attr = $ref->getAttributes();
 var_dump(count($attr));
 
index 30537e2c12a4e281f8e7d5b1f57930f4f9c407c5..3c0e1190ffec962be0217e54c6f0c55f2070b90f 100644 (file)
@@ -25,11 +25,11 @@ namespace Foo {
     use Doctrine\ORM\Mapping as ORM;
     use Doctrine\ORM\Attributes;
 
-    <<Entity("imported class")>>
-    <<ORM\Entity("imported namespace")>>
-    <<\Doctrine\ORM\Mapping\Entity("absolute from namespace")>>
-    <<\Entity("import absolute from global")>>
-    <<Attributes\Table()>>
+    @@Entity("imported class")
+    @@ORM\Entity("imported namespace")
+    @@\Doctrine\ORM\Mapping\Entity("absolute from namespace")
+    @@\Entity("import absolute from global")
+    @@Attributes\Table()
     function foo() {
     }
 }
index 712ec93beaf4f5184bf7a1cd37b1bf531bb06363..83f523182b1b5e802b50a5d8152e35fa50bd55b1 100644 (file)
@@ -3,7 +3,7 @@ Attributes can be converted into objects.
 --FILE--
 <?php
 
-<<Attribute(Attribute::TARGET_FUNCTION)>>
+@@Attribute(Attribute::TARGET_FUNCTION)
 class A1
 {
        public string $name;
@@ -16,7 +16,7 @@ class A1
        }
 }
 
-$ref = new \ReflectionFunction(<<A1('test')>> 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(<<A1>> function () { });
+$ref = new \ReflectionFunction(@@A1 function () { });
 
 try {
        $ref->getAttributes()[0]->newInstance();
@@ -36,7 +36,7 @@ try {
 
 echo "\n";
 
-$ref = new \ReflectionFunction(<<A1([])>> function () { });
+$ref = new \ReflectionFunction(@@A1([]) function () { });
 
 try {
        $ref->getAttributes()[0]->newInstance();
@@ -46,7 +46,7 @@ try {
 
 echo "\n";
 
-$ref = new \ReflectionFunction(<<A2>> function () { });
+$ref = new \ReflectionFunction(@@A2 function () { });
 
 try {
        $ref->getAttributes()[0]->newInstance();
@@ -56,13 +56,13 @@ try {
 
 echo "\n";
 
-<<Attribute>>
+@@Attribute
 class A3
 {
        private function __construct() { }
 }
 
-$ref = new \ReflectionFunction(<<A3>> function () { });
+$ref = new \ReflectionFunction(@@A3 function () { });
 
 try {
        $ref->getAttributes()[0]->newInstance();
@@ -72,10 +72,10 @@ try {
 
 echo "\n";
 
-<<Attribute>>
+@@Attribute
 class A4 { }
 
-$ref = new \ReflectionFunction(<<A4(1)>> function () { });
+$ref = new \ReflectionFunction(@@A4(1) function () { });
 
 try {
        $ref->getAttributes()[0]->newInstance();
@@ -87,7 +87,7 @@ echo "\n";
 
 class A5 { }
 
-$ref = new \ReflectionFunction(<<A5>> function () { });
+$ref = new \ReflectionFunction(@@A5 function () { });
 
 try {
        $ref->getAttributes()[0]->newInstance();
index 49c02d6e35017f3f4923bb62e67a31ea9a1e0d6c..69ee146b49f0340c7005f95c64f230346569cf86 100644 (file)
@@ -3,17 +3,17 @@ Attributes can be filtered by name and base type.
 --FILE--
 <?php
 
-$ref = new \ReflectionFunction(<<A1>> <<A2>> function () { });
+$ref = new \ReflectionFunction(@@A1 @@A2 function () { });
 $attr = $ref->getAttributes(A3::class);
 
 var_dump(count($attr));
 
-$ref = new \ReflectionFunction(<<A1>> <<A2>> function () { });
+$ref = new \ReflectionFunction(@@A1 @@A2 function () { });
 $attr = $ref->getAttributes(A2::class);
 
 var_dump(count($attr), $attr[0]->getName());
 
-$ref = new \ReflectionFunction(<<A1>> <<A2>> <<A2>> 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(<<A1>> <<A2>> <<A5>> 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(<<A1>> <<A2>> 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(<<A1>> <<A2>> 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(<<A1>> <<A2>> <<A3>> 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(<<A1>> <<A2>> <<A3>> 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));
index 20a800b9a70f24b859b0771b837cc485571c5ccf..21ea9783d1c26cc899fa2f4e318f33625779923e 100644 (file)
@@ -3,7 +3,7 @@ Attributes: Prevent Attribute on non classes
 --FILE--
 <?php
 
-<<Attribute>>
+@@Attribute
 function foo() {}
 --EXPECTF--
 Fatal error: Attribute "Attribute" cannot target function (allowed targets: class) in %s
index 38fc6389e070ce62f18151bb6051839088518596..51cb315d487c91bde695282317cd664e78ef23a2 100644 (file)
@@ -25,20 +25,22 @@ namespace {
 use Doctrine\ORM\Attributes as ORM;
 use Symfony\Component\Validator\Constraints as Assert;
 
-<<ORM\Entity>>
+@@ORM\Entity
 /** @ORM\Entity */
 class User
 {
     /** @ORM\Id @ORM\Column(type="integer"*) @ORM\GeneratedValue */
-    <<ORM\Id>><<ORM\Column("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.")
      */
-    <<ORM\Column("string", ORM\Column::UNIQUE)>>
-    <<Assert\Email(array("message" => "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"
      * )
      */
-    <<Assert\Range(["min" => 120, "max" => 180, "minMessage" => "You must be at least {{ limit }}cm tall to enter"])>>
-    <<ORM\Column(ORM\Column::T_INTEGER)>>
+    @@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)>>
+    @@ORM\ManyToMany(Phonenumber::class)
+    @@ORM\JoinTable("users_phonenumbers")
+    @@ORM\JoinColumn("user_id", "id")
+    @@ORM\InverseJoinColumn("phonenumber_id", "id", ORM\JoinColumn::UNIQUE)
     private $phonenumbers;
 }
 
index 50afa6c4f65c5aa22b1a975e7214a06eda58bdcd..fb30e2c486bd5c5b425de6d69409bb69406b73b6 100644 (file)
@@ -3,7 +3,7 @@ Attribute arguments support only const expressions.
 --FILE--
 <?php
 
-<<A1(foo())>>
+@@A1(foo())
 class C1 { }
 
 ?>
index 007cd5991e7d73ed2de5fb5cca22ba4f11e76355..25c943dea30e93f6f932e9b648d4468f5557a5d3 100644 (file)
@@ -3,10 +3,10 @@ Attributes comply with inheritance rules.
 --FILE--
 <?php
 
-<<A2>>
+@@A2
 class C1
 {
-       <<A1>>
+       @@A1
        public function foo() { }
 }
 
@@ -17,7 +17,7 @@ class C2 extends C1
 
 class C3 extends C1
 {
-       <<A1>>
+       @@A1
        public function bar() { }
 }
 
@@ -37,7 +37,7 @@ echo "\n";
 
 trait T1
 {
-       <<A2>>
+       @@A2
        public $a;
 }
 
index fe3539c8a9fb8b361995710558f7cb82e43fefa1..c811327d0cd9bfbe58860a1941e92f18a41f6922 100644 (file)
@@ -3,51 +3,51 @@ Attributes AST can be exported.
 --FILE--
 <?php
 
-assert(0 && ($a = <<A1>><<A2>> function ($a, <<A3(1)>> $b) { }));
+assert(0 && ($a = @@A1 @@A2 function ($a, @@A3(1) $b) { }));
 
-assert(0 && ($a = <<A1(1, 2, 1 + 2)>> fn () => 1));
+assert(0 && ($a = @@A1(1, 2, 1 + 2) fn () => 1));
 
-assert(0 && ($a = new <<A1>> class() {
-       <<A1>><<A2>> const FOO = 'foo';
-       <<A2>> public $x;
-       <<A3>> function a() { }
+assert(0 && ($a = new @@A1 class() {
+       @@A1@@A2 const FOO = 'foo';
+       @@A2 public $x;
+       @@A3 function a() { }
 }));
 
 assert(0 && ($a = function () {
-       <<A1>> class Test1 { }
-       <<A2>> interface Test2 { }
-       <<A3>> trait Test3 { }
+       @@A1 class Test1 { }
+       @@A2 interface Test2 { }
+       @@A3 trait Test3 { }
 }));
 
 ?>
 --EXPECTF--
-Warning: assert(): assert(0 && ($a = <<A1>> <<A2>> function ($a, <<A3(1)>> $b) {
+Warning: assert(): assert(0 && ($a = @@A1 @@A2 function ($a, @@A3(1) $b) {
 })) 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 = @@A1(1, 2, 1 + 2) fn() => 1)) failed in %s on line %d
 
-Warning: assert(): assert(0 && ($a = new <<A1>> class {
-    <<A1>>
-    <<A2>>
+Warning: assert(): assert(0 && ($a = new @@A1 class {
+    @@A1
+    @@A2
     public const FOO = 'foo';
-    <<A2>>
+    @@A2
     public $x;
-    <<A3>>
+    @@A3
     public function a() {
     }
 
 })) failed in %s on line %d
 
 Warning: assert(): assert(0 && ($a = function () {
-    <<A1>>
+    @@A1
     class Test1 {
     }
 
-    <<A2>>
+    @@A2
     interface Test2 {
     }
 
-    <<A3>>
+    @@A3
     trait Test3 {
     }
 
index 94764c2d6908829b904a980164792427c515fa09..be6c7a60da4f489c01f80d44ba2dea158a1b6955 100644 (file)
@@ -3,17 +3,17 @@ Attributes make use of class scope.
 --FILE--
 <?php
 
-<<A1(self::class, self::FOO)>>
+@@A1(self::class, self::FOO)
 class C1
 {
-       <<A1(self::class, self::FOO)>>
+       @@A1(self::class, self::FOO)
        private const FOO = 'foo';
 
-       <<A1(self::class, self::FOO)>>
+       @@A1(self::class, self::FOO)
        public $a;
 
-       <<A1(self::class, self::FOO)>>
-       public function bar(<<A1(self::class, self::FOO)>> $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)>>
+       @@A1(self::class, self::FOO)
        public function foo() { }
 }
 
@@ -58,10 +58,10 @@ class C3
 
        public static function foo()
        {
-               return new <<A1(self::class, self::FOO)>> class() {
+               return new @@A1(self::class, self::FOO) class() {
                        private const FOO = 'bar';
 
-                       <<A1(self::class, self::FOO)>>
+                       @@A1(self::class, self::FOO)
                        public function bar() { }
                };
        }
index 18ff93683aff9193aa58295abf0f13a55e852419..5da5c52a957794c070210a3ccd0dcfb054afe3a2 100644 (file)
@@ -5,7 +5,7 @@ Attributes cannot be applied to groups of class constants.
 
 class C1
 {
-       <<A1>>
+       @@A1
        public const A = 1, B = 2;
 }
 
index 493e4ae2601367281e68bd9aaf6bcbd607ff3493..52e3b0a92a8d7e682fd061640dd4cd0292299e92 100644 (file)
@@ -5,7 +5,7 @@ Attributes cannot be applied to groups of properties.
 
 class C1
 {
-       <<A1>>
+       @@A1
        public $x, $y;
 }
 
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 c96b49cbd7fc1bfa6d0e46316f32b43f902a2a39..6095d390d9963705cb014cb15e8fdad158849de7 100644 (file)
@@ -1,5 +1,5 @@
 --TEST--
-Attributes: Compiler Attributes can check for target declarations
+Attribute validation callback of internal attributes.
 --SKIPIF--
 <?php
 if (!extension_loaded('zend-test')) {
@@ -8,8 +8,8 @@ if (!extension_loaded('zend-test')) {
 --FILE--
 <?php
 
-<<ZendTestAttribute>>
+@@ZendTestAttribute
 function foo() {
 }
 --EXPECTF--
-Fatal error: Only classes can be marked with <<ZendTestAttribute>> in %s
+Fatal error: Only classes can be marked with @@ZendTestAttribute in %s
index f57b4b73b3c080c7586ee16f69beb799eff68f18..936a8ec338a74b298c77319d109db463118dec67 100644 (file)
@@ -14,7 +14,7 @@ class C1
 
        public static function foo()
        {
-               return <<A1(self::class, self::FOO)>> function (<<A1(self::class, self::FOO)>> $p) { };
+               return @@A1(self::class, self::FOO) function (@@A1(self::class, self::FOO) $p) { };
        }
 }
 
index a950c8658ab19ddf9e9c8644910a4581ac669f49..db2719d85a92cf6a4ba21a1859e62a9159ff6060 100644 (file)
@@ -3,7 +3,7 @@ Don't free uninitialized memory if a fatal error occurs in an attribute argument
 --FILE--
 <?php
 
-<<Attr(a->b::c)>>
+@@Attr(a->b::c)
 function test() {}
 
 ?>
index a259c06f3fe77c0ba502ebd1e72a1e974c33857a..9b8a3c22116dfd507a96cf02d1ccc785b5d8a31b 100644 (file)
@@ -3,7 +3,7 @@ Attribute name cannot be a variable
 --FILE--
 <?php
 
-<<$x>>
+@@$x
 class A {}
 
 ?>
index 48c5e2651bdf9a02eb39a687c3d4d6cda5a94cb2..1025b5008e20215c6300c39f325d9434eb682fbe 100644 (file)
@@ -3,17 +3,17 @@ Attributes expose and verify target and repeatable data.
 --FILE--
 <?php
 
-<<Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD)>>
+@@Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD)
 class A1 { }
 
-$ref = new \ReflectionFunction(<<A1>> 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 <<A1>> 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(<<A1>> <<A1>> 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)>>
+@@Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)
 class A2 { }
 
-$ref = new \ReflectionObject(new <<A2>> <<A2>> 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()));
index 06ed4d08fda477431aa8e84d87e89762e0d0c889..0f0b915ebe762a934b3867105a6d36ad12e5e9bb 100644 (file)
@@ -3,7 +3,7 @@ Attribute flags type is validated.
 --FILE--
 <?php
 
-<<Attribute("foo")>>
+@@Attribute("foo")
 class A1 { }
 
 ?>
index 1deb81e4d50221e01ec29c42eb874fc21c017b74..28ad550385d4fd2afeb2a151ccf6341a63041520 100644 (file)
@@ -3,7 +3,7 @@ Attribute flags value is validated.
 --FILE--
 <?php
 
-<<Attribute(-1)>>
+@@Attribute(-1)
 class A1 { }
 
 ?>
index 57a7287cae28027e709cf6d4f934009e246454f9..ce44527f62fd54060e9f5b14d1a5b3ee3ca0c2e5 100644 (file)
@@ -3,7 +3,7 @@ Attribute flags value is validated.
 --FILE--
 <?php
 
-<<Attribute(Foo::BAR)>>
+@@Attribute(Foo::BAR)
 class A1 { }
 
 ?>
index 746ceb3c697ebdf67992741c5aaee6dfa14b2db1..49a5ae68c8b14ef9b322ba2f55a22eecadb59697 100644 (file)
@@ -3,7 +3,7 @@ Internal attribute targets are validated.
 --FILE--
 <?php
 
-<<Attribute>>
+@@Attribute
 function a1() { }
 
 ?>
index 631f0b5054c8053e6bec1adaa043cbdb26ac121a..b3c83e810f2af42aff4d6d4204e7b2762a3babcf 100644 (file)
@@ -3,8 +3,8 @@ Internal attribute targets are validated.
 --FILE--
 <?php
 
-<<Attribute>>
-<<Attribute>>
+@@Attribute
+@@Attribute
 class A1 { }
 
 ?>
index 04a038d3e58a78dffd6963aef196155fb3cee9da..37f8fb5679aba32958e2dea1f10d9f0ce9bd25cd 100644 (file)
@@ -3,7 +3,7 @@ Cannot use unpacking in attribute argument list
 --FILE--
 <?php
 
-<<MyAttribute(...[1, 2, 3])>>
+@@MyAttribute(...[1, 2, 3])
 class Foo { }
 
 ?>
index c8f6adf0e83ee3eebdefd975ec0fb24af3212e6c..5ac47e08a8586ff313d3d1933f3d413e6612bedc 100644 (file)
@@ -3,12 +3,12 @@ Trailing comma in attribute argument list
 --FILE--
 <?php
 
-<<MyAttribute(
+@@MyAttribute(
        "there",
        "are",
        "many",
        "arguments",
-)>>
+)
 class Foo { }
 
 $ref = new \ReflectionClass(Foo::class);
index d85d8d3181d6f94ada9d0ed5b03ebbaae304505c..08c7117dafb4d0cbf00451f54eb198931fe7f686 100644 (file)
@@ -5,7 +5,7 @@ Attributes on promoted properties are assigned to both the property and paramete
 
 class Test {
     public function __construct(
-        <<NonNegative>>
+        @@NonNegative
         public int $num,
     ) {}
 }
index f6e0b476e95659efb1e33485241ca347a50b65d1..1affe08c37fe5e0638fc082636ef32e3eeb91d2b 100644 (file)
@@ -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);
index f8b4b6d862882b26913315b3d5ef51f61b76ee9e..55f2c0f49cf182092631b47c93fa743b56a7fce0 100644 (file)
@@ -178,6 +178,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
 %token <ident> 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:
index 3b6cc1dd676c7db697c4681e41450fad7852ee91..7f42159b466983b4c7ff92fc74df617452565144 100644 (file)
@@ -1422,6 +1422,10 @@ NEWLINE ("\r"|"\n"|"\r\n")
        RETURN_TOKEN_WITH_IDENT(T_RETURN);
 }
 
+<ST_IN_SCRIPTING>"@@" {
+       RETURN_TOKEN(T_ATTRIBUTE);
+}
+
 <ST_IN_SCRIPTING>"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 (file)
index 0000000..8795a4c
--- /dev/null
@@ -0,0 +1,20 @@
+--TEST--
+Attributes are exposed as tokens.
+--FILE--
+<?php
+
+$tokens = token_get_all('<?php @@A1(1, 2) class C1 { }');
+
+$attr = $tokens[1];
+var_dump(token_name(T_ATTRIBUTE));
+var_dump($attr[0] === T_ATTRIBUTE);
+var_dump($attr[1]);
+
+$class = $tokens[2];
+var_dump($class[1]);
+?>
+--EXPECT--
+string(11) "T_ATTRIBUTE"
+bool(true)
+string(2) "@@"
+string(2) "A1"
index 049433f4b7b1a509e8faba9871ccfbff93b15c20..5699c57566b7da54a3ba6a41f2f75220117bf3aa 100644 (file)
@@ -148,6 +148,7 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) {
        REGISTER_LONG_CONSTANT("T_METHOD_C", T_METHOD_C, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("T_FUNC_C", T_FUNC_C, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("T_NS_C", T_NS_C, CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("T_ATTRIBUTE", T_ATTRIBUTE, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("T_INC", T_INC, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("T_DEC", T_DEC, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("T_OBJECT_OPERATOR", T_OBJECT_OPERATOR, CONST_CS | CONST_PERSISTENT);
@@ -296,6 +297,7 @@ char *get_token_type_name(int token_type)
                case T_METHOD_C: return "T_METHOD_C";
                case T_FUNC_C: return "T_FUNC_C";
                case T_NS_C: return "T_NS_C";
+               case T_ATTRIBUTE: return "T_ATTRIBUTE";
                case T_INC: return "T_INC";
                case T_DEC: return "T_DEC";
                case T_OBJECT_OPERATOR: return "T_OBJECT_OPERATOR";
index 413a6a302f2af75f29f5408b8d134ff06aa8ffac..900c70f59d5af353d867bc2daa57d64bdb14fd44 100644 (file)
@@ -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 <<ZendTestAttribute>>");
+               zend_error(E_COMPILE_ERROR, "Only classes can be marked with @@ZendTestAttribute");
        }
 }