]> granicus.if.org Git - php/commitdiff
Class Name Resolution As Scalar Via "class" Keyword
authorLars Strojny <lstrojny@php.net>
Sat, 19 Jan 2013 00:00:47 +0000 (01:00 +0100)
committerLars Strojny <lstrojny@php.net>
Sat, 19 Jan 2013 00:00:47 +0000 (01:00 +0100)
13 files changed:
NEWS
Zend/tests/class_name_as_scalar.phpt [new file with mode: 0644]
Zend/tests/class_name_as_scalar_error_001.phpt [new file with mode: 0644]
Zend/tests/class_name_as_scalar_error_002.phpt [new file with mode: 0644]
Zend/tests/class_name_as_scalar_error_003.phpt [new file with mode: 0644]
Zend/tests/class_name_as_scalar_error_004.phpt [new file with mode: 0644]
Zend/tests/class_name_as_scalar_error_005.phpt [new file with mode: 0644]
Zend/tests/class_name_as_scalar_error_006.phpt [new file with mode: 0644]
Zend/zend_compile.c
Zend/zend_compile.h
Zend/zend_language_parser.y
Zend/zend_vm_def.h
Zend/zend_vm_execute.h

diff --git a/NEWS b/NEWS
index 6395b34f53e20edfb501259bf95c0f74d50b72de..3bb0373d3b72dc71540d72abebaa301eff02f0de 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,8 @@ PHP                                                                        NEWS
 
 - Core:
   . Fixed bug #63980 (object members get trimmed by zero bytes). (Laruence)
+  . Implemented RFC for Class Name Resolution As Scalar Via "class" Keyword.
+    (Ralph Schindler, Nikita Popov, Lars)
 
 - General improvements:
   . Fixed bug #63874 (Segfault if php_strip_whitespace has heredoc). (Pierrick)
diff --git a/Zend/tests/class_name_as_scalar.phpt b/Zend/tests/class_name_as_scalar.phpt
new file mode 100644 (file)
index 0000000..38e55b1
--- /dev/null
@@ -0,0 +1,77 @@
+--TEST--
+class name as scalar from ::class keyword
+--FILE--
+<?php
+
+namespace Foo\Bar {
+    class One {
+        // compile time constants
+        const A = self::class;
+        const B = Two::class;
+    }
+    class Two extends One {
+        public static function run() {
+            var_dump(self::class); // self compile time lookup
+            var_dump(static::class); // runtime lookup
+            var_dump(parent::class); // runtime lookup
+            var_dump(Baz::class); // default compile time lookup
+        }
+    }
+    class Three extends Two {
+        // compile time static lookups
+        public static function checkCompileTime(
+            $one = self::class,
+            $two = Baz::class,
+            $three = One::A,
+            $four = self::B
+        ) {
+            var_dump($one, $two, $three, $four);
+        }
+    }
+    echo "In NS\n";
+    var_dump(Moo::CLASS); // resolve in namespace
+}
+
+namespace {
+    use Bee\Bop as Moo,
+        Foo\Bar\One;
+    echo "Top\n";
+    var_dump(One::class); // resolve from use
+    var_dump(Boo::class); // resolve in global namespace
+    var_dump(Moo::CLASS); // resolve from use as
+    var_dump(\Moo::Class); // resolve fully qualified
+    $class = One::class; // assign class as scalar to var
+    $x = new $class; // create new class from original scalar assignment
+    var_dump($x);
+    Foo\Bar\Two::run(); // resolve runtime lookups
+    echo "Parent\n";
+    Foo\Bar\Three::run(); // resolve runtime lookups with inheritance
+    echo "Compile Check\n";
+    Foo\Bar\Three::checkCompileTime();
+}
+
+?>
+--EXPECTF--
+In NS
+string(11) "Foo\Bar\Moo"
+Top
+string(11) "Foo\Bar\One"
+string(3) "Boo"
+string(7) "Bee\Bop"
+string(3) "Moo"
+object(Foo\Bar\One)#1 (0) {
+}
+string(11) "Foo\Bar\Two"
+string(11) "Foo\Bar\Two"
+string(11) "Foo\Bar\One"
+string(11) "Foo\Bar\Baz"
+Parent
+string(11) "Foo\Bar\Two"
+string(13) "Foo\Bar\Three"
+string(11) "Foo\Bar\One"
+string(11) "Foo\Bar\Baz"
+Compile Check
+string(13) "Foo\Bar\Three"
+string(11) "Foo\Bar\Baz"
+string(11) "Foo\Bar\One"
+string(11) "Foo\Bar\Two"
diff --git a/Zend/tests/class_name_as_scalar_error_001.phpt b/Zend/tests/class_name_as_scalar_error_001.phpt
new file mode 100644 (file)
index 0000000..1c7aa7e
--- /dev/null
@@ -0,0 +1,13 @@
+--TEST--
+class name as scalar from ::class keyword error using static in class constant
+--FILE--
+<?php
+
+namespace Foo\Bar {
+    class One {
+        const Baz = static::class;
+    }
+}
+?>
+--EXPECTF--
+Fatal error: static::class cannot be used for compile-time class name resolution in %s on line %d
diff --git a/Zend/tests/class_name_as_scalar_error_002.phpt b/Zend/tests/class_name_as_scalar_error_002.phpt
new file mode 100644 (file)
index 0000000..59b7a2e
--- /dev/null
@@ -0,0 +1,13 @@
+--TEST--
+class name as scalar from ::class keyword error using parent in class constant
+--FILE--
+<?php
+
+namespace Foo\Bar {
+    class One {
+        const Baz = parent::class;
+    }
+}
+?>
+--EXPECTF--
+Fatal error: parent::class cannot be used for compile-time class name resolution in %s on line %d
diff --git a/Zend/tests/class_name_as_scalar_error_003.phpt b/Zend/tests/class_name_as_scalar_error_003.phpt
new file mode 100644 (file)
index 0000000..9299041
--- /dev/null
@@ -0,0 +1,13 @@
+--TEST--
+class name as scalar from ::class keyword error using static in method signature
+--FILE--
+<?php
+
+namespace Foo\Bar {
+    class One {
+        public function baz($x = static::class) {}
+    }
+}
+?>
+--EXPECTF--
+Fatal error: static::class cannot be used for compile-time class name resolution in %s on line %d
diff --git a/Zend/tests/class_name_as_scalar_error_004.phpt b/Zend/tests/class_name_as_scalar_error_004.phpt
new file mode 100644 (file)
index 0000000..c00037f
--- /dev/null
@@ -0,0 +1,13 @@
+--TEST--
+class name as scalar from ::class keyword error using parent in method signature
+--FILE--
+<?php
+
+namespace Foo\Bar {
+    class One {
+        public function baz($x = parent::class) {}
+    }
+}
+?>
+--EXPECTF--
+Fatal error: parent::class cannot be used for compile-time class name resolution in %s on line %d
diff --git a/Zend/tests/class_name_as_scalar_error_005.phpt b/Zend/tests/class_name_as_scalar_error_005.phpt
new file mode 100644 (file)
index 0000000..39de69f
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+class name as scalar from ::class keyword error using static non class context
+--FILE--
+<?php
+
+$x = static::class;
+
+?>
+--EXPECTF--
+Fatal error: Cannot access static::class when no class scope is active in %s on line %d
\ No newline at end of file
diff --git a/Zend/tests/class_name_as_scalar_error_006.phpt b/Zend/tests/class_name_as_scalar_error_006.phpt
new file mode 100644 (file)
index 0000000..a4cc9a5
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+class name as scalar from ::class keyword error using parent in non class context
+--FILE--
+<?php
+
+$x = parent::class;
+
+?>
+--EXPECTF--
+Fatal error: Cannot access parent::class when no class scope is active in %s on line %d
index 43b891695e0f45a73e38ada84b6c0f393791669e..b574ad6b69180a31f0aef9aeea26cbc4e236beb7 100644 (file)
@@ -2119,6 +2119,53 @@ void zend_resolve_non_class_name(znode *element_name, zend_bool check_namespace
 }
 /* }}} */
 
+void zend_do_resolve_class_name(znode *result, znode *class_name, int is_static TSRMLS_DC) /* {{{ */
+{
+       char *lcname;
+       int lctype;
+       znode constant_name;
+
+       lcname = zend_str_tolower_dup(Z_STRVAL(class_name->u.constant), class_name->u.constant.value.str.len);
+       lctype = zend_get_class_fetch_type(lcname, strlen(lcname));
+       switch (lctype) {
+               case ZEND_FETCH_CLASS_SELF:
+                       if (!CG(active_class_entry)) {
+                               zend_error(E_COMPILE_ERROR, "Cannot access self::class when no class scope is active");
+                       }
+                       zval_dtor(&class_name->u.constant);
+                       class_name->op_type = IS_CONST;
+                       ZVAL_STRINGL(&class_name->u.constant, CG(active_class_entry)->name, CG(active_class_entry)->name_length, 1);
+                       *result = *class_name;
+                       break;
+        case ZEND_FETCH_CLASS_STATIC:
+        case ZEND_FETCH_CLASS_PARENT:
+                       if (is_static) {
+                               zend_error(E_COMPILE_ERROR,
+                                       "%s::class cannot be used for compile-time class name resolution",
+                                       lctype == ZEND_FETCH_CLASS_STATIC ? "static" : "parent"
+                                       );
+                       }
+                       if (!CG(active_class_entry)) {
+                               zend_error(E_COMPILE_ERROR,
+                                       "Cannot access %s::class when no class scope is active",
+                                       lctype == ZEND_FETCH_CLASS_STATIC ? "static" : "parent"
+                                       );
+                       }
+                       constant_name.op_type = IS_CONST;
+                       ZVAL_STRINGL(&constant_name.u.constant, "class", sizeof("class")-1, 1);
+                       zend_do_fetch_constant(result, class_name, &constant_name, ZEND_RT, 1 TSRMLS_CC);
+                       break;
+               case ZEND_FETCH_CLASS_DEFAULT:
+                       zend_resolve_class_name(class_name, ZEND_FETCH_CLASS_GLOBAL, 1);
+                       *result = *class_name;
+                       break;
+       }
+
+       efree(lcname);
+
+}
+/* }}} */
+
 void zend_resolve_class_name(znode *class_name, ulong fetch_type, int check_ns_name TSRMLS_DC) /* {{{ */
 {
        char *compound;
index 0f58b5550041e78f426701ed74bdabf29a245b27..8042dd54eee816d1dc284885c1c428da6d83dcef 100644 (file)
@@ -638,6 +638,8 @@ void zend_verify_namespace(TSRMLS_D);
 void zend_do_use(znode *name, znode *new_name, int is_global TSRMLS_DC);
 void zend_do_end_compilation(TSRMLS_D);
 
+void zend_do_resolve_class_name(znode *result, znode *class_name, int is_static TSRMLS_DC);
+
 void zend_do_label(znode *label TSRMLS_DC);
 void zend_do_goto(const znode *label TSRMLS_DC);
 void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2 TSRMLS_DC);
index 781d806fe7c58e8817d1c4970f47fc577f241c6e..ccbc9b174c9d4fa499436293a2c6bfdd3805de86 100644 (file)
@@ -942,6 +942,7 @@ common_scalar:
 
 static_scalar: /* compile-time evaluated scalars */
                common_scalar           { $$ = $1; }
+       |       static_class_name_scalar        { $$ = $1; }
        |       namespace_name          { zend_do_fetch_constant(&$$, NULL, &$1, ZEND_CT, 1 TSRMLS_CC); }
        |       T_NAMESPACE T_NS_SEPARATOR namespace_name { $$.op_type = IS_CONST; ZVAL_EMPTY_STRING(&$$.u.constant);  zend_do_build_namespace_name(&$$, &$$, &$3 TSRMLS_CC); $3 = $$; zend_do_fetch_constant(&$$, NULL, &$3, ZEND_CT, 0 TSRMLS_CC); }
        |       T_NS_SEPARATOR namespace_name { char *tmp = estrndup(Z_STRVAL($2.u.constant), Z_STRLEN($2.u.constant)+1); memcpy(&(tmp[1]), Z_STRVAL($2.u.constant), Z_STRLEN($2.u.constant)+1); tmp[0] = '\\'; efree(Z_STRVAL($2.u.constant)); Z_STRVAL($2.u.constant) = tmp; ++Z_STRLEN($2.u.constant); zend_do_fetch_constant(&$$, NULL, &$2, ZEND_CT, 0 TSRMLS_CC); }
@@ -959,6 +960,7 @@ static_class_constant:
 
 scalar:
                T_STRING_VARNAME                { $$ = $1; }
+       |       class_name_scalar       { $$ = $1; }
        |       class_constant          { $$ = $1; }
        |       namespace_name  { zend_do_fetch_constant(&$$, NULL, &$1, ZEND_RT, 1 TSRMLS_CC); }
        |       T_NAMESPACE T_NS_SEPARATOR namespace_name { $$.op_type = IS_CONST; ZVAL_EMPTY_STRING(&$$.u.constant);  zend_do_build_namespace_name(&$$, &$$, &$3 TSRMLS_CC); $3 = $$; zend_do_fetch_constant(&$$, NULL, &$3, ZEND_RT, 0 TSRMLS_CC); }
@@ -1200,6 +1202,14 @@ class_constant:
        |       variable_class_name T_PAAMAYIM_NEKUDOTAYIM T_STRING { zend_do_fetch_constant(&$$, &$1, &$3, ZEND_RT, 0 TSRMLS_CC); }
 ;
 
+static_class_name_scalar:
+       class_name T_PAAMAYIM_NEKUDOTAYIM T_CLASS { zend_do_resolve_class_name(&$$, &$1, 1 TSRMLS_CC); }
+;
+
+class_name_scalar:
+       class_name T_PAAMAYIM_NEKUDOTAYIM T_CLASS { zend_do_resolve_class_name(&$$, &$1, 0 TSRMLS_CC); }
+;
+
 %%
 
 /* Copy to YYRES the contents of YYSTR after stripping away unnecessary
index 3f9cc126ed9b22b04228c03843a170be3fe23610..25ac453125c57ef77da4f51439a3a7a873f4252f 100644 (file)
@@ -3563,6 +3563,9 @@ ZEND_VM_HANDLER(99, ZEND_FETCH_CONSTANT, VAR|CONST|UNUSED, CONST)
                        }
                        ZVAL_COPY_VALUE(&EX_T(opline->result.var).tmp_var, *value);
                        zval_copy_ctor(&EX_T(opline->result.var).tmp_var);
+               } else if (Z_STRLEN_P(opline->op2.zv) == sizeof("class")-1 && strcmp(Z_STRVAL_P(opline->op2.zv), "class") == 0) {
+                       /* "class" is assigned as a case-sensitive keyword from zend_do_resolve_class_name */
+                       ZVAL_STRINGL(&EX_T(opline->result.var).tmp_var, ce->name, ce->name_length, 1);
                } else {
                        zend_error_noreturn(E_ERROR, "Undefined class constant '%s'", Z_STRVAL_P(opline->op2.zv));
                }
index 614249f9934a38f0a06dfb26721960e9fcc202fd..ab69ee292bdab44afdc1f6589a68402f58e2bc24 100644 (file)
@@ -3731,6 +3731,9 @@ static int ZEND_FASTCALL  ZEND_FETCH_CONSTANT_SPEC_CONST_CONST_HANDLER(ZEND_OPCO
                        }
                        ZVAL_COPY_VALUE(&EX_T(opline->result.var).tmp_var, *value);
                        zval_copy_ctor(&EX_T(opline->result.var).tmp_var);
+               } else if (Z_STRLEN_P(opline->op2.zv) == sizeof("class")-1 && strcmp(Z_STRVAL_P(opline->op2.zv), "class") == 0) {
+                       /* "class" is assigned as a case-sensitive keyword from zend_do_resolve_class_name */
+                       ZVAL_STRINGL(&EX_T(opline->result.var).tmp_var, ce->name, ce->name_length, 1);
                } else {
                        zend_error_noreturn(E_ERROR, "Undefined class constant '%s'", Z_STRVAL_P(opline->op2.zv));
                }
@@ -15595,6 +15598,9 @@ static int ZEND_FASTCALL  ZEND_FETCH_CONSTANT_SPEC_VAR_CONST_HANDLER(ZEND_OPCODE
                        }
                        ZVAL_COPY_VALUE(&EX_T(opline->result.var).tmp_var, *value);
                        zval_copy_ctor(&EX_T(opline->result.var).tmp_var);
+               } else if (Z_STRLEN_P(opline->op2.zv) == sizeof("class")-1 && strcmp(Z_STRVAL_P(opline->op2.zv), "class") == 0) {
+                       /* "class" is assigned as a case-sensitive keyword from zend_do_resolve_class_name */
+                       ZVAL_STRINGL(&EX_T(opline->result.var).tmp_var, ce->name, ce->name_length, 1);
                } else {
                        zend_error_noreturn(E_ERROR, "Undefined class constant '%s'", Z_STRVAL_P(opline->op2.zv));
                }
@@ -25170,6 +25176,9 @@ static int ZEND_FASTCALL  ZEND_FETCH_CONSTANT_SPEC_UNUSED_CONST_HANDLER(ZEND_OPC
                        }
                        ZVAL_COPY_VALUE(&EX_T(opline->result.var).tmp_var, *value);
                        zval_copy_ctor(&EX_T(opline->result.var).tmp_var);
+               } else if (Z_STRLEN_P(opline->op2.zv) == sizeof("class")-1 && strcmp(Z_STRVAL_P(opline->op2.zv), "class") == 0) {
+                       /* "class" is assigned as a case-sensitive keyword from zend_do_resolve_class_name */
+                       ZVAL_STRINGL(&EX_T(opline->result.var).tmp_var, ce->name, ce->name_length, 1);
                } else {
                        zend_error_noreturn(E_ERROR, "Undefined class constant '%s'", Z_STRVAL_P(opline->op2.zv));
                }